@nocobase/plugin-file-manager 2.1.31 → 2.2.0-alpha.10
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/client/UseOriginalUrlRadio.d.ts +12 -0
- package/dist/client/index.js +1 -1
- package/dist/client/schemas/storageTypes/ali-oss.d.ts +12 -0
- package/dist/client/schemas/storageTypes/common.d.ts +12 -0
- package/dist/client/schemas/storageTypes/index.d.ts +48 -0
- package/dist/client/schemas/storageTypes/local.d.ts +12 -0
- package/dist/client/schemas/storageTypes/s3.d.ts +12 -0
- package/dist/client/schemas/storageTypes/tx-cos.d.ts +12 -0
- package/dist/client-v2/125.9cdb7cf3ef4712ef.js +10 -0
- package/dist/client-v2/{229.bd72c2d7aa088310.js → 229.297c431f63a29cb2.js} +1 -1
- package/dist/client-v2/{336.1dd1b32466d0c778.js → 336.04d06f2cdcc3d0b5.js} +1 -1
- package/dist/client-v2/{43.eb45d53ba3e9828b.js → 43.1ec3e5bf1118d239.js} +1 -1
- package/dist/client-v2/450.d49b00f3c0388c74.js +10 -0
- package/dist/client-v2/{929.d7e783304cc1f236.js → 929.3744119eff70b3a3.js} +1 -1
- package/dist/client-v2/components/PublicAccessField.d.ts +19 -0
- package/dist/client-v2/components/UseOriginalUrlField.d.ts +25 -0
- package/dist/client-v2/components/index.d.ts +2 -0
- package/dist/client-v2/index.d.ts +3 -2
- package/dist/client-v2/index.js +1 -1
- package/dist/client-v2/models/DisplayPreviewFieldModel.d.ts +22 -0
- package/dist/client-v2/models/UploadActionModel.d.ts +26 -0
- package/dist/client-v2/previewer/filePreviewTypes.d.ts +15 -1
- package/dist/externalVersion.js +13 -10
- package/dist/locale/de-DE.json +7 -0
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/es-ES.json +7 -0
- package/dist/locale/fr-FR.json +7 -0
- package/dist/locale/hu-HU.json +7 -0
- package/dist/locale/id-ID.json +7 -0
- package/dist/locale/it-IT.json +7 -0
- package/dist/locale/ja-JP.json +7 -0
- package/dist/locale/ko-KR.json +7 -0
- package/dist/locale/nl-NL.json +7 -0
- package/dist/locale/pt-BR.json +7 -0
- package/dist/locale/ru-RU.json +7 -0
- package/dist/locale/tr-TR.json +7 -0
- package/dist/locale/uk-UA.json +7 -0
- package/dist/locale/vi-VN.json +7 -0
- package/dist/locale/zh-CN.json +7 -0
- package/dist/locale/zh-TW.json +7 -0
- package/dist/node_modules/@aws-sdk/client-s3/dist-cjs/index.js +59 -116
- package/dist/node_modules/@aws-sdk/client-s3/package.json +1 -1
- package/dist/node_modules/@aws-sdk/lib-storage/dist-cjs/index.js +59 -116
- package/dist/node_modules/@aws-sdk/lib-storage/package.json +1 -1
- package/dist/node_modules/@aws-sdk/s3-request-presigner/LICENSE +201 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-cjs/index.js +11113 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-es/constants.js +9 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-es/getSignedUrl.js +66 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-es/index.js +2 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-es/presigner.js +58 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/constants.d.ts +9 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/getSignedUrl.d.ts +6 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/index.d.ts +2 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/presigner.d.ts +15 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/ts3.4/constants.d.ts +9 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/ts3.4/getSignedUrl.d.ts +11 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/ts3.4/index.d.ts +2 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/dist-types/ts3.4/presigner.d.ts +40 -0
- package/dist/node_modules/@aws-sdk/s3-request-presigner/package.json +1 -0
- package/dist/node_modules/ali-oss/package.json +1 -1
- package/dist/node_modules/cos-nodejs-sdk-v5/package.json +1 -1
- package/dist/node_modules/mime-match/package.json +1 -1
- package/dist/node_modules/mime-types/package.json +1 -1
- package/dist/node_modules/mkdirp/package.json +1 -1
- package/dist/node_modules/url-join/package.json +1 -1
- package/dist/server/actions/get-file.d.ts +13 -0
- package/dist/server/actions/get-file.js +142 -0
- package/dist/server/actions/index.d.ts +1 -3
- package/dist/server/actions/index.js +8 -1
- package/dist/server/commands/repair-filenames.js +34 -14
- package/dist/server/file-access.d.ts +11 -0
- package/dist/server/file-access.js +134 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +7 -1
- package/dist/server/resolve-file-access-filter.d.ts +11 -0
- package/dist/server/resolve-file-access-filter.js +63 -0
- package/dist/server/server.d.ts +23 -2
- package/dist/server/server.js +95 -15
- package/dist/server/storages/ali-oss.d.ts +2 -1
- package/dist/server/storages/ali-oss.js +12 -0
- package/dist/server/storages/index.d.ts +7 -1
- package/dist/server/storages/index.js +28 -5
- package/dist/server/storages/local.d.ts +2 -2
- package/dist/server/storages/local.js +9 -3
- package/dist/server/storages/s3.d.ts +2 -1
- package/dist/server/storages/s3.js +14 -0
- package/dist/server/storages/tx-cos.d.ts +2 -1
- package/dist/server/storages/tx-cos.js +27 -0
- package/dist/server/temporary-access.d.ts +31 -0
- package/dist/server/temporary-access.js +169 -0
- package/dist/server/utils.d.ts +27 -0
- package/dist/server/utils.js +83 -2
- package/package.json +5 -3
- package/dist/client-v2/125.0b8eef1f19b87042.js +0 -10
- package/dist/client-v2/450.c060947ac34d26e1.js +0 -10
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var file_access_exports = {};
|
|
28
|
+
__export(file_access_exports, {
|
|
29
|
+
createFileAccessMiddleware: () => createFileAccessMiddleware
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(file_access_exports);
|
|
32
|
+
var import_utils = require("./utils");
|
|
33
|
+
const DEFAULT_APP_NAME = "main";
|
|
34
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]*$/;
|
|
35
|
+
function stripPublicPath(path) {
|
|
36
|
+
const publicPath = (0, import_utils.trimPublicPath)(process.env.APP_PUBLIC_PATH);
|
|
37
|
+
if (publicPath && (path === publicPath || path.startsWith(`${publicPath}/`))) {
|
|
38
|
+
return path.slice(publicPath.length) || "/";
|
|
39
|
+
}
|
|
40
|
+
return path;
|
|
41
|
+
}
|
|
42
|
+
function parseFileAccessPath(path) {
|
|
43
|
+
const normalizedPath = stripPublicPath(path);
|
|
44
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
45
|
+
if (segments[0] !== "files") {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (segments.length !== 5) {
|
|
49
|
+
throw Object.assign(new Error("Invalid file URL"), { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const fileIdSegment = decodeURIComponent(segments[4]);
|
|
53
|
+
const extnameIndex = fileIdSegment.lastIndexOf(".");
|
|
54
|
+
const extname = extnameIndex > 0 ? (0, import_utils.normalizeFileAccessExtname)(fileIdSegment.slice(extnameIndex)) : "";
|
|
55
|
+
const params = {
|
|
56
|
+
appName: decodeURIComponent(segments[1]),
|
|
57
|
+
dataSourceKey: decodeURIComponent(segments[2]),
|
|
58
|
+
collectionName: decodeURIComponent(segments[3]),
|
|
59
|
+
id: extname ? fileIdSegment.slice(0, extnameIndex) : fileIdSegment,
|
|
60
|
+
extname: extname || void 0
|
|
61
|
+
};
|
|
62
|
+
if (!IDENTIFIER_PATTERN.test(params.appName) || !IDENTIFIER_PATTERN.test(params.dataSourceKey) || !IDENTIFIER_PATTERN.test(params.collectionName)) {
|
|
63
|
+
throw Object.assign(new Error("Invalid file URL"), { status: 404 });
|
|
64
|
+
}
|
|
65
|
+
return params;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw Object.assign(new Error("Invalid file URL"), { status: 404 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getApiBasePath(dataSource) {
|
|
71
|
+
var _a, _b;
|
|
72
|
+
return (0, import_utils.trimPublicPath)((_b = (_a = dataSource.resourceManager) == null ? void 0 : _a.options) == null ? void 0 : _b.prefix);
|
|
73
|
+
}
|
|
74
|
+
function createFileAccessMiddleware(plugin) {
|
|
75
|
+
return async function handleFileAccess(ctx, next) {
|
|
76
|
+
const pathParams = parseFileAccessPath(ctx.path);
|
|
77
|
+
if (!pathParams) {
|
|
78
|
+
return next();
|
|
79
|
+
}
|
|
80
|
+
if (!["GET", "HEAD"].includes(ctx.method)) {
|
|
81
|
+
return ctx.throw(405);
|
|
82
|
+
}
|
|
83
|
+
const temporaryAccessToken = ctx.query["temporaryAccessToken"];
|
|
84
|
+
const params = {
|
|
85
|
+
...pathParams,
|
|
86
|
+
preview: ctx.query.preview === "1",
|
|
87
|
+
temporaryAccess: typeof temporaryAccessToken === "string",
|
|
88
|
+
temporaryAccessToken: typeof temporaryAccessToken === "string" ? temporaryAccessToken : void 0
|
|
89
|
+
};
|
|
90
|
+
if (params.preview && params.temporaryAccess) {
|
|
91
|
+
return ctx.throw(404);
|
|
92
|
+
}
|
|
93
|
+
if (params.appName !== (plugin.app.name || DEFAULT_APP_NAME)) {
|
|
94
|
+
return ctx.throw(404);
|
|
95
|
+
}
|
|
96
|
+
const dataSource = plugin.app.dataSourceManager.get(params.dataSourceKey);
|
|
97
|
+
if (!dataSource) {
|
|
98
|
+
return ctx.throw(404);
|
|
99
|
+
}
|
|
100
|
+
const originalPath = ctx.path;
|
|
101
|
+
const originalDataSourceHeader = ctx.req.headers["x-data-source"];
|
|
102
|
+
const originalDb = ctx.db;
|
|
103
|
+
const originalOptionalAuth = ctx.state.optionalAuth;
|
|
104
|
+
const originalFileAccess = ctx.state.fileAccess;
|
|
105
|
+
const originalSkipAuthCheck = ctx.skipAuthCheck;
|
|
106
|
+
try {
|
|
107
|
+
ctx.path = `${getApiBasePath(dataSource)}/${encodeURIComponent(
|
|
108
|
+
params.collectionName
|
|
109
|
+
)}:getFile/${encodeURIComponent(params.id)}`;
|
|
110
|
+
ctx.req.headers["x-data-source"] = params.dataSourceKey;
|
|
111
|
+
ctx.db = plugin.app.db;
|
|
112
|
+
ctx.state.optionalAuth = !params.temporaryAccess;
|
|
113
|
+
ctx.skipAuthCheck = params.temporaryAccess ? true : originalSkipAuthCheck;
|
|
114
|
+
ctx.state.fileAccess = params;
|
|
115
|
+
await plugin.app.dataSourceManager.middleware()(ctx, async () => {
|
|
116
|
+
});
|
|
117
|
+
} finally {
|
|
118
|
+
ctx.path = originalPath;
|
|
119
|
+
if (originalDataSourceHeader === void 0) {
|
|
120
|
+
delete ctx.req.headers["x-data-source"];
|
|
121
|
+
} else {
|
|
122
|
+
ctx.req.headers["x-data-source"] = originalDataSourceHeader;
|
|
123
|
+
}
|
|
124
|
+
ctx.db = originalDb;
|
|
125
|
+
ctx.state.optionalAuth = originalOptionalAuth;
|
|
126
|
+
ctx.state.fileAccess = originalFileAccess;
|
|
127
|
+
ctx.skipAuthCheck = originalSkipAuthCheck;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
132
|
+
0 && (module.exports = {
|
|
133
|
+
createFileAccessMiddleware
|
|
134
|
+
});
|
package/dist/server/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { StorageEngine } from 'multer';
|
|
10
10
|
export * from '../constants';
|
|
11
11
|
export { AttachmentModel, default, PluginFileManagerServer, StorageModel } from './server';
|
|
12
|
+
export type { FileAccessAuthorizeParams, FileAccessAuthorizer } from './server';
|
|
12
13
|
export { cloudFilenameGetter } from './utils';
|
|
13
|
-
export { StorageType, GetFileStreamOptions } from './storages';
|
|
14
|
+
export { appendDownloadResponse, getDownloadContentDisposition, StorageType, GetFileStreamOptions, GetFileURLOptions, } from './storages';
|
|
14
15
|
export { StorageEngine };
|
package/dist/server/index.js
CHANGED
|
@@ -39,12 +39,15 @@ var server_exports = {};
|
|
|
39
39
|
__export(server_exports, {
|
|
40
40
|
AttachmentModel: () => import_server.AttachmentModel,
|
|
41
41
|
GetFileStreamOptions: () => import_storages.GetFileStreamOptions,
|
|
42
|
+
GetFileURLOptions: () => import_storages.GetFileURLOptions,
|
|
42
43
|
PluginFileManagerServer: () => import_server.PluginFileManagerServer,
|
|
43
44
|
StorageEngine: () => import_multer.StorageEngine,
|
|
44
45
|
StorageModel: () => import_server.StorageModel,
|
|
45
46
|
StorageType: () => import_storages.StorageType,
|
|
47
|
+
appendDownloadResponse: () => import_storages.appendDownloadResponse,
|
|
46
48
|
cloudFilenameGetter: () => import_utils.cloudFilenameGetter,
|
|
47
|
-
default: () => import_server.default
|
|
49
|
+
default: () => import_server.default,
|
|
50
|
+
getDownloadContentDisposition: () => import_storages.getDownloadContentDisposition
|
|
48
51
|
});
|
|
49
52
|
module.exports = __toCommonJS(server_exports);
|
|
50
53
|
var import_multer = require("multer");
|
|
@@ -56,10 +59,13 @@ var import_storages = require("./storages");
|
|
|
56
59
|
0 && (module.exports = {
|
|
57
60
|
AttachmentModel,
|
|
58
61
|
GetFileStreamOptions,
|
|
62
|
+
GetFileURLOptions,
|
|
59
63
|
PluginFileManagerServer,
|
|
60
64
|
StorageEngine,
|
|
61
65
|
StorageModel,
|
|
62
66
|
StorageType,
|
|
67
|
+
appendDownloadResponse,
|
|
63
68
|
cloudFilenameGetter,
|
|
69
|
+
getDownloadContentDisposition,
|
|
64
70
|
...require("../constants")
|
|
65
71
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@nocobase/actions';
|
|
10
|
+
import type { Collection } from '@nocobase/database';
|
|
11
|
+
export declare function resolveFileAccessFilter(ctx: Context, collection: Collection, filter: object): Promise<object>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
var resolve_file_access_filter_exports = {};
|
|
38
|
+
__export(resolve_file_access_filter_exports, {
|
|
39
|
+
resolveFileAccessFilter: () => resolveFileAccessFilter
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(resolve_file_access_filter_exports);
|
|
42
|
+
var import_acl = require("@nocobase/acl");
|
|
43
|
+
var import_lodash = __toESM(require("lodash"));
|
|
44
|
+
async function resolveFileAccessFilter(ctx, collection, filter) {
|
|
45
|
+
try {
|
|
46
|
+
const permission = await ctx.dataSource.acl.resolveActionParams(ctx, {
|
|
47
|
+
resourceName: collection.name,
|
|
48
|
+
actionName: "view",
|
|
49
|
+
params: { filter }
|
|
50
|
+
});
|
|
51
|
+
const mergedParams = permission.mergedParams || { filter };
|
|
52
|
+
return import_lodash.default.cloneDeep(mergedParams.filter || filter);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof import_acl.NoPermissionError) {
|
|
55
|
+
ctx.throw(403, "No permissions");
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
61
|
+
0 && (module.exports = {
|
|
62
|
+
resolveFileAccessFilter
|
|
63
|
+
});
|
package/dist/server/server.d.ts
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
/// <reference types="node" />
|
|
10
|
+
import type { Context } from '@nocobase/actions';
|
|
10
11
|
import { Model, Transactionable } from '@nocobase/database';
|
|
11
12
|
import { Plugin } from '@nocobase/server';
|
|
12
13
|
import { Registry } from '@nocobase/utils';
|
|
13
14
|
import { Readable } from 'stream';
|
|
14
|
-
import { AttachmentModel, GetFileStreamOptions, StorageClassType, StorageModel } from './storages';
|
|
15
|
+
import { AttachmentModel, GetFileStreamOptions, GetFileURLOptions, StorageClassType, StorageModel } from './storages';
|
|
15
16
|
export type * from './storages';
|
|
17
|
+
type AttachmentRecord = Model & AttachmentModel;
|
|
16
18
|
export type FileRecordOptions = {
|
|
17
19
|
collectionName: string;
|
|
18
20
|
filePath: string;
|
|
@@ -26,12 +28,26 @@ export type UploadFileOptions = {
|
|
|
26
28
|
subPath?: string;
|
|
27
29
|
documentRoot?: string;
|
|
28
30
|
};
|
|
31
|
+
export type FileAccessAuthorizeParams = {
|
|
32
|
+
appName: string;
|
|
33
|
+
dataSourceKey: string;
|
|
34
|
+
collectionName: string;
|
|
35
|
+
id: string;
|
|
36
|
+
preview: boolean;
|
|
37
|
+
};
|
|
38
|
+
export type FileAccessAuthorizer = {
|
|
39
|
+
name: string;
|
|
40
|
+
authorize: (ctx: Context, params: FileAccessAuthorizeParams) => Promise<boolean | null | undefined> | boolean | null | undefined;
|
|
41
|
+
};
|
|
29
42
|
export declare class PluginFileManagerServer extends Plugin {
|
|
30
43
|
storageTypes: Registry<StorageClassType>;
|
|
31
44
|
storagesCache: Map<string | number, StorageModel>;
|
|
45
|
+
protected fileAccessAuthorizers: Registry<FileAccessAuthorizer>;
|
|
32
46
|
static staticImport(): Promise<void>;
|
|
33
47
|
afterDestroy: (record: Model, options: any) => Promise<void>;
|
|
34
48
|
registerStorageType(type: string, Type: StorageClassType): void;
|
|
49
|
+
registerFileAccessAuthorizer(authorizer: FileAccessAuthorizer): void;
|
|
50
|
+
authorizeFileAccess(ctx: Context, params: FileAccessAuthorizeParams): Promise<boolean>;
|
|
35
51
|
createFileRecord(options: FileRecordOptions): Promise<any>;
|
|
36
52
|
parseStorage(instance: any): any;
|
|
37
53
|
uploadFile(options: UploadFileOptions): Promise<{
|
|
@@ -51,7 +67,12 @@ export declare class PluginFileManagerServer extends Plugin {
|
|
|
51
67
|
handleSyncMessage(message: any): Promise<void>;
|
|
52
68
|
beforeLoad(): Promise<void>;
|
|
53
69
|
load(): Promise<void>;
|
|
54
|
-
|
|
70
|
+
setFileResponseURLs(record: AttachmentRecord, collectionName: string): Promise<void>;
|
|
71
|
+
getPermanentFileURL(file: AttachmentModel, preview?: boolean, options?: {
|
|
72
|
+
dataSourceKey?: string;
|
|
73
|
+
collectionName?: string;
|
|
74
|
+
}): any;
|
|
75
|
+
getFileURL(file: AttachmentModel, preview?: boolean, options?: GetFileURLOptions): Promise<any>;
|
|
55
76
|
isPublicAccessStorage(storageName: any): Promise<boolean>;
|
|
56
77
|
getFileStream(file: AttachmentModel, options?: GetFileStreamOptions): Promise<{
|
|
57
78
|
stream: Readable;
|
package/dist/server/server.js
CHANGED
|
@@ -48,6 +48,7 @@ var import_server = require("@nocobase/server");
|
|
|
48
48
|
var import_utils = require("@nocobase/utils");
|
|
49
49
|
var import_constants = require("../constants");
|
|
50
50
|
var import_actions = __toESM(require("./actions"));
|
|
51
|
+
var import_file_access = require("./file-access");
|
|
51
52
|
var import_attachment_interface = require("./interfaces/attachment-interface");
|
|
52
53
|
var import_ali_oss = __toESM(require("./storages/ali-oss"));
|
|
53
54
|
var import_local = __toESM(require("./storages/local"));
|
|
@@ -55,7 +56,20 @@ var import_s3 = __toESM(require("./storages/s3"));
|
|
|
55
56
|
var import_tx_cos = __toESM(require("./storages/tx-cos"));
|
|
56
57
|
var import_utils2 = require("./utils");
|
|
57
58
|
var import_repair_filenames = require("./commands/repair-filenames");
|
|
59
|
+
var import_temporary_access = require("./temporary-access");
|
|
58
60
|
const DEFAULT_STORAGE_TYPE = import_constants.STORAGE_TYPE_LOCAL;
|
|
61
|
+
const DEFAULT_APP_NAME = "main";
|
|
62
|
+
const DEFAULT_DATA_SOURCE_KEY = "main";
|
|
63
|
+
class FileModel extends import_database.Model {
|
|
64
|
+
toJSON() {
|
|
65
|
+
const values = super.toJSON();
|
|
66
|
+
const local = this.get("local");
|
|
67
|
+
if (typeof local === "boolean") {
|
|
68
|
+
values.local = local;
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
59
73
|
class FileDeleteError extends Error {
|
|
60
74
|
data;
|
|
61
75
|
constructor(message, data) {
|
|
@@ -72,6 +86,7 @@ function supportsFileStream(dataSource) {
|
|
|
72
86
|
class PluginFileManagerServer extends import_server.Plugin {
|
|
73
87
|
storageTypes = new import_utils.Registry();
|
|
74
88
|
storagesCache = /* @__PURE__ */ new Map();
|
|
89
|
+
fileAccessAuthorizers = new import_utils.Registry();
|
|
75
90
|
static async staticImport() {
|
|
76
91
|
import_server.Application.addCommand(import_repair_filenames.registerRepairFilenamesCommand);
|
|
77
92
|
}
|
|
@@ -104,6 +119,17 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
104
119
|
registerStorageType(type, Type) {
|
|
105
120
|
this.storageTypes.register(type, Type);
|
|
106
121
|
}
|
|
122
|
+
registerFileAccessAuthorizer(authorizer) {
|
|
123
|
+
this.fileAccessAuthorizers.register(authorizer.name, authorizer);
|
|
124
|
+
}
|
|
125
|
+
async authorizeFileAccess(ctx, params) {
|
|
126
|
+
for (const authorizer of this.fileAccessAuthorizers.getValues()) {
|
|
127
|
+
if (await authorizer.authorize(ctx, params)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
107
133
|
async createFileRecord(options) {
|
|
108
134
|
const { values, storageName, subPath, collectionName, filePath, transaction } = options;
|
|
109
135
|
const collection = this.db.getCollection(collectionName);
|
|
@@ -195,7 +221,8 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
195
221
|
}
|
|
196
222
|
}
|
|
197
223
|
async beforeLoad() {
|
|
198
|
-
|
|
224
|
+
(0, import_temporary_access.getTemporaryFileAccessExpiresIn)();
|
|
225
|
+
this.db.registerModels({ FileModel });
|
|
199
226
|
this.db.on("beforeDefineCollection", (options) => {
|
|
200
227
|
if (options.template === "file") {
|
|
201
228
|
options.model = "FileModel";
|
|
@@ -205,7 +232,27 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
205
232
|
if (collection.options.template !== "file") {
|
|
206
233
|
return;
|
|
207
234
|
}
|
|
235
|
+
collection.setField("local", {
|
|
236
|
+
type: "virtual",
|
|
237
|
+
hidden: true
|
|
238
|
+
});
|
|
239
|
+
const idField = collection.getField("id");
|
|
240
|
+
if (idField) {
|
|
241
|
+
idField.options.deletable = false;
|
|
242
|
+
idField.options.updatable = false;
|
|
243
|
+
}
|
|
244
|
+
const extnameField = collection.getField("extname");
|
|
245
|
+
if (extnameField) {
|
|
246
|
+
extnameField.options.updatable = false;
|
|
247
|
+
}
|
|
248
|
+
collection.model.afterCreate(async (model) => {
|
|
249
|
+
await this.setFileResponseURLs(model, collection.name);
|
|
250
|
+
});
|
|
208
251
|
collection.model.beforeUpdate((model) => {
|
|
252
|
+
if (model.changed("extname")) {
|
|
253
|
+
model.set("extname", model.previous("extname"));
|
|
254
|
+
model.changed("extname", false);
|
|
255
|
+
}
|
|
209
256
|
if (!model.changed("url") || !model.changed("preview")) {
|
|
210
257
|
return;
|
|
211
258
|
}
|
|
@@ -292,6 +339,7 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
292
339
|
}
|
|
293
340
|
});
|
|
294
341
|
this.app.db.interfaceManager.registerInterfaceType("attachment", import_attachment_interface.AttachmentInterface);
|
|
342
|
+
this.app.use((0, import_file_access.createFileAccessMiddleware)(this), { tag: "fileAccess", before: "dataSource", after: "dataWrapping" });
|
|
295
343
|
this.db.on("afterFind", async (instances) => {
|
|
296
344
|
var _a, _b, _c;
|
|
297
345
|
if (!instances) {
|
|
@@ -303,32 +351,67 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
303
351
|
const collection = this.db.getCollection(name);
|
|
304
352
|
if ((collection == null ? void 0 : collection.name) === "attachments" || ((_c = collection == null ? void 0 : collection.options) == null ? void 0 : _c.template) === "file") {
|
|
305
353
|
for (const record of records) {
|
|
306
|
-
|
|
307
|
-
const previewUrl = await this.getFileURL(record, true);
|
|
308
|
-
record.set("url", url);
|
|
309
|
-
record.set("preview", previewUrl);
|
|
310
|
-
record.dataValues.preview = previewUrl;
|
|
354
|
+
await this.setFileResponseURLs(record, collection.name);
|
|
311
355
|
}
|
|
312
356
|
}
|
|
313
357
|
}
|
|
314
358
|
});
|
|
315
359
|
}
|
|
316
|
-
async
|
|
360
|
+
async setFileResponseURLs(record, collectionName) {
|
|
361
|
+
var _a;
|
|
362
|
+
const storage = this.storagesCache.get(record.get("storageId"));
|
|
363
|
+
const useOriginalUrl = Boolean((_a = storage == null ? void 0 : storage.options) == null ? void 0 : _a.useOriginalUrl);
|
|
364
|
+
const [url, previewUrl] = useOriginalUrl ? await Promise.all([this.getFileURL(record), this.getFileURL(record, true)]) : [
|
|
365
|
+
this.getPermanentFileURL(record, false, { collectionName }),
|
|
366
|
+
this.getPermanentFileURL(record, true, { collectionName })
|
|
367
|
+
];
|
|
368
|
+
record.set("url", url);
|
|
369
|
+
record.set("preview", previewUrl);
|
|
370
|
+
record.dataValues.preview = previewUrl;
|
|
371
|
+
if (storage == null ? void 0 : storage.type) {
|
|
372
|
+
record.set("local", storage.type === import_constants.STORAGE_TYPE_LOCAL);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
getPermanentFileURL(file, preview = false, options = {}) {
|
|
317
376
|
if (!file.storageId) {
|
|
318
377
|
return (0, import_utils2.encodeURL)(file.url);
|
|
319
378
|
}
|
|
379
|
+
const publicPath = (0, import_utils2.getFilePublicBasePath)();
|
|
380
|
+
const appName = this.app.name || DEFAULT_APP_NAME;
|
|
381
|
+
const dataSourceKey = options.dataSourceKey || DEFAULT_DATA_SOURCE_KEY;
|
|
382
|
+
const collectionName = options.collectionName || (0, import_utils2.getRecordCollectionName)(file);
|
|
383
|
+
const id = (0, import_utils2.getFileAccessPathSegment)(file.id, file.extname);
|
|
384
|
+
const url = `${publicPath}/files/${encodeURIComponent(String(appName))}/${encodeURIComponent(
|
|
385
|
+
String(dataSourceKey)
|
|
386
|
+
)}/${encodeURIComponent(String(collectionName))}/${id}`;
|
|
387
|
+
return preview ? `${url}?preview=1` : url;
|
|
388
|
+
}
|
|
389
|
+
async getFileURL(file, preview = false, options = {}) {
|
|
390
|
+
if (!file.storageId) {
|
|
391
|
+
return (0, import_utils2.encodeURL)(file.url);
|
|
392
|
+
}
|
|
393
|
+
const storageFile = (0, import_utils2.getFilePlainObject)(file);
|
|
394
|
+
if ((0, import_utils2.isPermanentFileAccessURL)(storageFile.url, storageFile, this.app.name || DEFAULT_APP_NAME)) {
|
|
395
|
+
storageFile.url = void 0;
|
|
396
|
+
}
|
|
320
397
|
const storage = this.storagesCache.get(file.storageId);
|
|
321
398
|
if (!storage) {
|
|
322
|
-
|
|
399
|
+
throw new Error("[file-manager] no linked or default storage provided");
|
|
323
400
|
}
|
|
324
401
|
const storageType = this.storageTypes.get(storage.type);
|
|
402
|
+
if (!storageType) {
|
|
403
|
+
throw new Error(`[file-manager] storage type "${storage.type}" is not defined`);
|
|
404
|
+
}
|
|
325
405
|
return new storageType(storage).getFileURL(
|
|
326
|
-
|
|
327
|
-
Boolean(
|
|
406
|
+
storageFile,
|
|
407
|
+
Boolean(
|
|
408
|
+
storageFile.mimetype && (0, import_mime_match.default)(storageFile.mimetype, "image/*") && preview && storage.options.thumbnailRule
|
|
409
|
+
),
|
|
410
|
+
options
|
|
328
411
|
);
|
|
329
412
|
}
|
|
330
413
|
async isPublicAccessStorage(storageName) {
|
|
331
|
-
var _a;
|
|
414
|
+
var _a, _b;
|
|
332
415
|
const storageRepository = this.db.getRepository("storages");
|
|
333
416
|
const storages = await storageRepository.findOne({
|
|
334
417
|
filter: { default: true }
|
|
@@ -344,10 +427,7 @@ class PluginFileManagerServer extends import_server.Plugin {
|
|
|
344
427
|
});
|
|
345
428
|
}
|
|
346
429
|
storage = this.parseStorage(storage);
|
|
347
|
-
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
return !!((_a = storage.options) == null ? void 0 : _a.public);
|
|
430
|
+
return Boolean(((_a = storage.options) == null ? void 0 : _a.public) || ((_b = storage.options) == null ? void 0 : _b.useOriginalUrl));
|
|
351
431
|
}
|
|
352
432
|
async getFileStream(file, options) {
|
|
353
433
|
var _a;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import { AttachmentModel, StorageType } from '.';
|
|
9
|
+
import { AttachmentModel, GetFileURLOptions, StorageType } from '.';
|
|
10
10
|
declare function getRandomFilename(req: any, file: any, cb: any): void;
|
|
11
11
|
declare class AliYunOssStorage {
|
|
12
12
|
client: any;
|
|
@@ -34,6 +34,7 @@ export default class extends StorageType {
|
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
36
|
make(): AliYunOssStorage;
|
|
37
|
+
getFileURL(file: AttachmentModel, preview?: boolean, options?: GetFileURLOptions): Promise<any>;
|
|
37
38
|
exists(record: AttachmentModel): Promise<boolean>;
|
|
38
39
|
copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
|
|
39
40
|
delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
|
|
@@ -123,6 +123,18 @@ class ali_oss_default extends import__.StorageType {
|
|
|
123
123
|
filename: (0, import_utils.cloudFilenameGetter)(this.storage)
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
+
async getFileURL(file, preview = false, options = {}) {
|
|
127
|
+
if (!options.download) {
|
|
128
|
+
return super.getFileURL(file, preview, options);
|
|
129
|
+
}
|
|
130
|
+
const { client } = this.make();
|
|
131
|
+
return client.signatureUrl((0, import_utils.getFileKey)(file), {
|
|
132
|
+
expires: this.storage.options.signedUrlExpires || 900,
|
|
133
|
+
response: {
|
|
134
|
+
"content-disposition": (0, import__.getDownloadContentDisposition)(file.filename)
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
126
138
|
async exists(record) {
|
|
127
139
|
const { client } = this.make();
|
|
128
140
|
try {
|
|
@@ -27,6 +27,7 @@ export interface AttachmentModel {
|
|
|
27
27
|
id?: number;
|
|
28
28
|
title: string;
|
|
29
29
|
filename: string;
|
|
30
|
+
extname?: string;
|
|
30
31
|
mimetype?: string;
|
|
31
32
|
path: string;
|
|
32
33
|
url?: string;
|
|
@@ -59,7 +60,7 @@ export declare abstract class StorageType {
|
|
|
59
60
|
meta: {};
|
|
60
61
|
storageId: number;
|
|
61
62
|
};
|
|
62
|
-
getFileURL(file: AttachmentModel, preview?: boolean): string | Promise<string>;
|
|
63
|
+
getFileURL(file: AttachmentModel, preview?: boolean, options?: GetFileURLOptions): string | Promise<string>;
|
|
63
64
|
getFileStream(file: AttachmentModel, options?: GetFileStreamOptions): Promise<{
|
|
64
65
|
stream: Readable;
|
|
65
66
|
contentType?: string;
|
|
@@ -71,3 +72,8 @@ export type StorageClassType = {
|
|
|
71
72
|
export type GetFileStreamOptions = {
|
|
72
73
|
requestOptions?: any;
|
|
73
74
|
};
|
|
75
|
+
export type GetFileURLOptions = {
|
|
76
|
+
download?: boolean;
|
|
77
|
+
};
|
|
78
|
+
export declare function getDownloadContentDisposition(filename: string): string;
|
|
79
|
+
export declare function appendDownloadResponse(url: string, filename: string): string;
|
|
@@ -36,7 +36,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var storages_exports = {};
|
|
38
38
|
__export(storages_exports, {
|
|
39
|
-
StorageType: () => StorageType
|
|
39
|
+
StorageType: () => StorageType,
|
|
40
|
+
appendDownloadResponse: () => appendDownloadResponse,
|
|
41
|
+
getDownloadContentDisposition: () => getDownloadContentDisposition
|
|
40
42
|
});
|
|
41
43
|
module.exports = __toCommonJS(storages_exports);
|
|
42
44
|
var import_axios = __toESM(require("axios"));
|
|
@@ -79,12 +81,12 @@ class StorageType {
|
|
|
79
81
|
};
|
|
80
82
|
return data;
|
|
81
83
|
}
|
|
82
|
-
getFileURL(file, preview) {
|
|
84
|
+
getFileURL(file, preview, options = {}) {
|
|
83
85
|
if (file.url && (0, import_utils.isURL)(file.url)) {
|
|
84
86
|
if (preview && this.storage.options.thumbnailRule) {
|
|
85
87
|
return (0, import_utils2.encodeURL)(file.url) + this.storage.options.thumbnailRule;
|
|
86
88
|
}
|
|
87
|
-
return (0, import_utils2.encodeURL)(file.url);
|
|
89
|
+
return options.download ? appendDownloadResponse(file.url, file.filename) : (0, import_utils2.encodeURL)(file.url);
|
|
88
90
|
}
|
|
89
91
|
const keys = [
|
|
90
92
|
this.storage.baseUrl,
|
|
@@ -92,7 +94,8 @@ class StorageType {
|
|
|
92
94
|
(0, import_utils2.ensureUrlEncoded)(file.filename),
|
|
93
95
|
preview && this.storage.options.thumbnailRule
|
|
94
96
|
].filter(Boolean);
|
|
95
|
-
|
|
97
|
+
const url = (0, import_url_join.default)(keys);
|
|
98
|
+
return options.download ? appendDownloadResponse(url, file.filename) : url;
|
|
96
99
|
}
|
|
97
100
|
async getFileStream(file, options) {
|
|
98
101
|
var _a;
|
|
@@ -116,7 +119,27 @@ class StorageType {
|
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
}
|
|
122
|
+
function getDownloadContentDisposition(filename) {
|
|
123
|
+
const name = import_path.default.basename(filename || "file");
|
|
124
|
+
const fallback = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_") || "file";
|
|
125
|
+
const encoded = encodeURIComponent(name).replace(
|
|
126
|
+
/['()*]/g,
|
|
127
|
+
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`
|
|
128
|
+
);
|
|
129
|
+
return `attachment; filename="${fallback}"; filename*=UTF-8''${encoded}`;
|
|
130
|
+
}
|
|
131
|
+
function appendDownloadResponse(url, filename) {
|
|
132
|
+
try {
|
|
133
|
+
const target = new URL(url);
|
|
134
|
+
target.searchParams.set("response-content-disposition", getDownloadContentDisposition(filename));
|
|
135
|
+
return target.toString();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return url;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
119
140
|
// Annotate the CommonJS export names for ESM import in node:
|
|
120
141
|
0 && (module.exports = {
|
|
121
|
-
StorageType
|
|
142
|
+
StorageType,
|
|
143
|
+
appendDownloadResponse,
|
|
144
|
+
getDownloadContentDisposition
|
|
122
145
|
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
/// <reference types="node" />
|
|
10
10
|
import multer from 'multer';
|
|
11
11
|
import type { Readable } from 'stream';
|
|
12
|
-
import { AttachmentModel, StorageType } from '.';
|
|
12
|
+
import { AttachmentModel, GetFileURLOptions, StorageType } from '.';
|
|
13
13
|
export declare function normalizeLocalStoragePath(storagePath?: unknown): string;
|
|
14
14
|
export declare function getDocumentRoot(storage: any): string;
|
|
15
15
|
export declare function resolveSafePath(documentRoot: string, filePath?: string, filename?: string): string;
|
|
@@ -34,7 +34,7 @@ export default class extends StorageType {
|
|
|
34
34
|
exists(record: AttachmentModel): Promise<boolean>;
|
|
35
35
|
copy(source: AttachmentModel, target: AttachmentModel): Promise<void>;
|
|
36
36
|
delete(records: AttachmentModel[]): Promise<[number, AttachmentModel[]]>;
|
|
37
|
-
getFileURL(file: AttachmentModel, preview?: boolean): Promise<any>;
|
|
37
|
+
getFileURL(file: AttachmentModel, preview?: boolean, options?: GetFileURLOptions): Promise<any>;
|
|
38
38
|
getFileStream(file: AttachmentModel): Promise<{
|
|
39
39
|
stream: Readable;
|
|
40
40
|
contentType?: string;
|
|
@@ -53,6 +53,11 @@ var import__ = require(".");
|
|
|
53
53
|
var import_constants = require("../../constants");
|
|
54
54
|
var import_utils2 = require("../utils");
|
|
55
55
|
const DEFAULT_BASE_URL = "/storage/uploads";
|
|
56
|
+
function appendDownloadParam(url) {
|
|
57
|
+
const [urlWithoutHash, hash] = url.split("#", 2);
|
|
58
|
+
const separator = urlWithoutHash.includes("?") ? "&" : "?";
|
|
59
|
+
return `${urlWithoutHash}${separator}download=1${hash ? `#${hash}` : ""}`;
|
|
60
|
+
}
|
|
56
61
|
function pathError(message) {
|
|
57
62
|
const error = new Error(message);
|
|
58
63
|
error.code = "PATH_TRAVERSAL";
|
|
@@ -182,12 +187,13 @@ class local_default extends import__.StorageType {
|
|
|
182
187
|
);
|
|
183
188
|
return [count, undeleted];
|
|
184
189
|
}
|
|
185
|
-
async getFileURL(file, preview = false) {
|
|
186
|
-
const url = await super.getFileURL(file, preview);
|
|
190
|
+
async getFileURL(file, preview = false, options = {}) {
|
|
191
|
+
const url = await super.getFileURL(file, preview, options);
|
|
187
192
|
if ((0, import_utils.isURL)(url)) {
|
|
188
193
|
return url;
|
|
189
194
|
}
|
|
190
|
-
|
|
195
|
+
const publicUrl = (0, import_url_join.default)(process.env.APP_PUBLIC_PATH, url);
|
|
196
|
+
return options.download ? appendDownloadParam(publicUrl) : publicUrl;
|
|
191
197
|
}
|
|
192
198
|
async getFileStream(file) {
|
|
193
199
|
const filePath = resolveSafePath(getDocumentRoot(this.storage), file.path, file.filename);
|