@nocobase/plugin-file-manager 0.9.4-alpha.2 → 0.10.0-alpha.2

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 (49) hide show
  1. package/lib/client/FileStorage.d.ts +2 -1
  2. package/lib/client/FileStorage.js +6 -6
  3. package/lib/client/StorageOptions.d.ts +1 -1
  4. package/lib/client/StorageOptions.js +18 -21
  5. package/lib/client/index.d.ts +2 -2
  6. package/lib/client/index.js +12 -7
  7. package/lib/client/initializers/UploadActionInitializer.d.ts +2 -1
  8. package/lib/client/initializers/UploadActionInitializer.js +1 -1
  9. package/lib/client/interfaces/attachment.d.ts +2 -0
  10. package/lib/client/interfaces/attachment.js +139 -0
  11. package/lib/client/locale/en-US.d.ts +22 -0
  12. package/lib/client/locale/en-US.js +28 -0
  13. package/lib/client/locale/index.js +0 -10
  14. package/lib/client/locale/ja-JP.d.ts +19 -0
  15. package/lib/client/locale/ja-JP.js +25 -0
  16. package/lib/client/locale/ru-RU.d.ts +19 -0
  17. package/lib/client/locale/ru-RU.js +25 -0
  18. package/lib/client/locale/tr-TR.d.ts +18 -0
  19. package/lib/client/locale/tr-TR.js +24 -0
  20. package/lib/client/locale/zh-CN.d.ts +25 -3
  21. package/lib/client/locale/zh-CN.js +25 -4
  22. package/lib/client/schemas/storage.js +41 -18
  23. package/lib/client/templates/file.js +1 -1
  24. package/lib/server/actions/attachments.d.ts +3 -0
  25. package/lib/server/actions/{upload.js → attachments.js} +140 -141
  26. package/lib/server/actions/index.d.ts +3 -0
  27. package/lib/server/actions/index.js +22 -0
  28. package/lib/server/collections/attachments.js +1 -0
  29. package/lib/server/collections/storages.js +3 -2
  30. package/lib/server/constants.d.ts +1 -1
  31. package/lib/server/constants.js +3 -3
  32. package/lib/server/rules/mimetype.d.ts +1 -1
  33. package/lib/server/rules/mimetype.js +1 -1
  34. package/lib/server/server.d.ts +1 -0
  35. package/lib/server/server.js +18 -6
  36. package/lib/server/storages/ali-oss.d.ts +2 -0
  37. package/lib/server/storages/ali-oss.js +12 -0
  38. package/lib/server/storages/index.d.ts +29 -4
  39. package/lib/server/storages/index.js +17 -7
  40. package/lib/server/storages/local.d.ts +2 -0
  41. package/lib/server/storages/local.js +34 -5
  42. package/lib/server/storages/s3.d.ts +2 -0
  43. package/lib/server/storages/s3.js +21 -0
  44. package/lib/server/storages/tx-cos.d.ts +2 -0
  45. package/lib/server/storages/tx-cos.js +25 -0
  46. package/package.json +10 -7
  47. package/lib/client/FileStorageShortcut.d.ts +0 -1
  48. package/lib/client/FileStorageShortcut.js +0 -91
  49. package/lib/server/actions/upload.d.ts +0 -4
@@ -4,14 +4,35 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- const locale = {
7
+ var _default = {
8
8
  'File collection': '文件数据表',
9
9
  'File name': '文件名',
10
10
  'Extension name': '扩展名',
11
11
  Size: '文件大小',
12
- 'Mime Type': 'Mime 类型',
12
+ 'MIME type': 'MIME 类型',
13
13
  URL: 'URL',
14
- 'File storage': '文件存储'
14
+ 'File storage': '文件存储',
15
+ 'File manager': '文件管理器',
16
+ Attachment: '附件',
17
+ 'Allow uploading multiple files': '允许上传多个文件',
18
+ Storage: '存储空间',
19
+ Storages: '存储空间',
20
+ 'Storage name': '存储空间标识',
21
+ 'Storage type': '存储类型',
22
+ 'Default storage': '默认存储空间',
23
+ 'Storage base URL': '访问 URL 基础',
24
+ Destination: '上传目标文件夹',
25
+ 'Use the built-in static file server': '使用内置静态文件服务',
26
+ 'Local storage': '本地存储',
27
+ 'Aliyun OSS': '阿里云 OSS',
28
+ 'Amazon S3': '亚马逊 S3',
29
+ 'Tencent COS': '腾讯云 COS',
30
+ Region: '区域',
31
+ Bucket: '存储桶',
32
+ Path: '相对路径',
33
+ Filename: '文件名',
34
+ 'Will be used for API': '将用于 API',
35
+ 'Default storage will be used when not selected': '留空将使用默认存储空间',
36
+ 'Keep file in storage when destroy record': '删除记录时保留文件'
15
37
  };
16
- var _default = locale;
17
38
  exports.default = _default;
@@ -18,6 +18,7 @@ function _client() {
18
18
  };
19
19
  return data;
20
20
  }
21
+ var _locale = require("../locale");
21
22
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
22
23
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
23
24
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -30,7 +31,7 @@ const collection = {
30
31
  name: 'title',
31
32
  interface: 'input',
32
33
  uiSchema: {
33
- title: '{{t("Storage display name")}}',
34
+ title: '{{t("Title")}}',
34
35
  type: 'string',
35
36
  'x-component': 'Input',
36
37
  required: true
@@ -40,7 +41,8 @@ const collection = {
40
41
  name: 'name',
41
42
  interface: 'input',
42
43
  uiSchema: {
43
- title: '{{t("Storage name")}}',
44
+ title: `{{t("Storage name", { ns: "${_locale.NAMESPACE}" })}}`,
45
+ descriptions: `{{t("Will be used for API", { ns: "${_locale.NAMESPACE}" })}}`,
44
46
  type: 'string',
45
47
  'x-component': 'Input'
46
48
  }
@@ -49,21 +51,21 @@ const collection = {
49
51
  name: 'type',
50
52
  interface: 'select',
51
53
  uiSchema: {
52
- title: '{{t("Storage type")}}',
54
+ title: `{{t("Storage type", { ns: "${_locale.NAMESPACE}" })}}`,
53
55
  type: 'string',
54
56
  'x-component': 'Select',
55
57
  required: true,
56
58
  enum: [{
57
- label: '{{t("Local storage")}}',
59
+ label: `{{t("Local storage", { ns: "${_locale.NAMESPACE}" })}}`,
58
60
  value: 'local'
59
61
  }, {
60
- label: '{{t("Aliyun OSS")}}',
62
+ label: `{{t("Aliyun OSS", { ns: "${_locale.NAMESPACE}" })}}`,
61
63
  value: 'ali-oss'
62
64
  }, {
63
- label: '{{t("Amazon S3")}}',
65
+ label: `{{t("Amazon S3", { ns: "${_locale.NAMESPACE}" })}}`,
64
66
  value: 's3'
65
67
  }, {
66
- label: '{{t("Tencent COS")}}',
68
+ label: `{{t("Tencent COS", { ns: "${_locale.NAMESPACE}" })}}`,
67
69
  value: 'tx-cos'
68
70
  }]
69
71
  }
@@ -72,7 +74,7 @@ const collection = {
72
74
  name: 'baseUrl',
73
75
  interface: 'input',
74
76
  uiSchema: {
75
- title: '{{t("Storage base URL")}}',
77
+ title: `{{t("Storage base URL", { ns: "${_locale.NAMESPACE}" })}}`,
76
78
  type: 'string',
77
79
  'x-component': 'Input',
78
80
  required: true
@@ -82,7 +84,7 @@ const collection = {
82
84
  name: 'path',
83
85
  interface: 'input',
84
86
  uiSchema: {
85
- title: '{{t("Path")}}',
87
+ title: `{{t("Path", { ns: "${_locale.NAMESPACE}" })}}`,
86
88
  type: 'string',
87
89
  'x-component': 'Input'
88
90
  }
@@ -91,7 +93,16 @@ const collection = {
91
93
  name: 'default',
92
94
  interface: 'boolean',
93
95
  uiSchema: {
94
- title: '{{t("Default storage")}}',
96
+ title: `{{t("Default storage", { ns: "${_locale.NAMESPACE}" })}}`,
97
+ type: 'boolean',
98
+ 'x-component': 'Checkbox'
99
+ }
100
+ }, {
101
+ type: 'boolean',
102
+ name: 'paranoid',
103
+ interface: 'boolean',
104
+ uiSchema: {
105
+ title: `{{t("Keep file in storage when destroy record", { ns: "${_locale.NAMESPACE}" })}}`,
95
106
  type: 'boolean',
96
107
  'x-component': 'Checkbox'
97
108
  }
@@ -137,14 +148,14 @@ const storageSchema = {
137
148
  'x-component-props': {
138
149
  useAction: '{{ cm.useBulkDestroyAction }}',
139
150
  confirm: {
140
- title: "{{t('Delete storage')}}",
151
+ title: "{{t('Delete')}}",
141
152
  content: "{{t('Are you sure you want to delete it?')}}"
142
153
  }
143
154
  }
144
155
  },
145
156
  create: {
146
157
  type: 'void',
147
- title: '{{t("Add storage")}}',
158
+ title: '{{t("Add new")}}',
148
159
  'x-component': 'Action',
149
160
  'x-component-props': {
150
161
  type: 'primary'
@@ -166,7 +177,7 @@ const storageSchema = {
166
177
  }));
167
178
  }
168
179
  },
169
- title: '{{t("Add storage")}}',
180
+ title: '{{t("Add new")}}',
170
181
  properties: {
171
182
  title: {
172
183
  'x-component': 'CollectionField',
@@ -197,7 +208,13 @@ const storageSchema = {
197
208
  'x-component': 'CollectionField',
198
209
  'x-decorator': 'FormItem',
199
210
  title: '',
200
- 'x-content': '{{t("Default storage")}}'
211
+ 'x-content': `{{t("Default storage", { ns: "${_locale.NAMESPACE}" })}}`
212
+ },
213
+ paranoid: {
214
+ title: '',
215
+ 'x-component': 'CollectionField',
216
+ 'x-decorator': 'FormItem',
217
+ 'x-content': `{{t("Keep file in storage when destroy record", { ns: "${_locale.NAMESPACE}" })}}`
201
218
  },
202
219
  footer: {
203
220
  type: 'void',
@@ -301,7 +318,7 @@ const storageSchema = {
301
318
  'x-decorator-props': {
302
319
  useValues: '{{ cm.useValuesFromRecord }}'
303
320
  },
304
- title: '{{t("Edit storage")}}',
321
+ title: '{{t("Edit")}}',
305
322
  properties: {
306
323
  title: {
307
324
  'x-component': 'CollectionField',
@@ -333,7 +350,13 @@ const storageSchema = {
333
350
  title: '',
334
351
  'x-component': 'CollectionField',
335
352
  'x-decorator': 'FormItem',
336
- 'x-content': '{{t("Default storage")}}'
353
+ 'x-content': `{{t("Default storage", { ns: "${_locale.NAMESPACE}" })}}`
354
+ },
355
+ paranoid: {
356
+ title: '',
357
+ 'x-component': 'CollectionField',
358
+ 'x-decorator': 'FormItem',
359
+ 'x-content': `{{t("Keep file in storage when destroy record", { ns: "${_locale.NAMESPACE}" })}}`
337
360
  },
338
361
  footer: {
339
362
  type: 'void',
@@ -366,8 +389,8 @@ const storageSchema = {
366
389
  'x-component': 'Action.Link',
367
390
  'x-component-props': {
368
391
  confirm: {
369
- title: "{{t('Delete role')}}",
370
- content: "{{t('Are you sure you want to delete it?')}}"
392
+ title: '{{t("Delete")}}',
393
+ content: '{{t("Are you sure you want to delete it?")}}'
371
394
  },
372
395
  useAction: '{{cm.useDestroyAction}}'
373
396
  }
@@ -85,7 +85,7 @@ const file = {
85
85
  deletable: false,
86
86
  uiSchema: {
87
87
  type: 'string',
88
- title: `{{t("Mime type", { ns: "${_locale.NAMESPACE}" })}}`,
88
+ title: `{{t("MIME type", { ns: "${_locale.NAMESPACE}" })}}`,
89
89
  'x-component': 'Input',
90
90
  'x-read-pretty': true
91
91
  }
@@ -0,0 +1,3 @@
1
+ import { Context, Next } from '@nocobase/actions';
2
+ export declare function createMiddleware(ctx: Context, next: Next): Promise<any>;
3
+ export declare function destroyMiddleware(ctx: Context, next: Next): Promise<any>;
@@ -3,9 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.createAction = createAction;
7
- exports.middleware = middleware;
8
- exports.uploadAction = uploadAction;
6
+ exports.createMiddleware = createMiddleware;
7
+ exports.destroyMiddleware = destroyMiddleware;
9
8
  function _multer() {
10
9
  const data = _interopRequireDefault(require("@koa/multer"));
11
10
  _multer = function _multer() {
@@ -27,186 +26,186 @@ const _excluded = ["size"];
27
26
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
28
27
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
29
28
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
29
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
30
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
30
31
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
31
32
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
32
33
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
33
34
  function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
34
35
  function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
35
- function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
36
- function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
37
36
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
38
37
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
39
- function getRules(ctx) {
40
- const resourceField = ctx.resourceField;
41
- if (!resourceField) {
42
- return ctx.storage.rules;
43
- }
44
- const _ref = resourceField.options.attachment || {},
45
- _ref$rules = _ref.rules,
46
- rules = _ref$rules === void 0 ? {} : _ref$rules;
47
- return Object.assign({}, ctx.storage.rules, rules);
48
- }
49
38
  // TODO(optimize): 需要优化错误处理,计算失败后需要抛出对应错误,以便程序处理
50
- function getFileFilter(ctx) {
39
+ function getFileFilter(storage) {
51
40
  return (req, file, cb) => {
52
41
  // size 交给 limits 处理
53
- const _getRules = getRules(ctx),
54
- size = _getRules.size,
55
- rules = _objectWithoutProperties(_getRules, _excluded);
42
+ const _storage$rules = storage.rules,
43
+ size = _storage$rules.size,
44
+ rules = _objectWithoutProperties(_storage$rules, _excluded);
56
45
  const ruleKeys = Object.keys(rules);
57
- const result = !ruleKeys.length || !ruleKeys.some(key => typeof Rules[key] !== 'function' || !Rules[key](file, rules[key], ctx));
46
+ const result = !ruleKeys.length || !ruleKeys.some(key => typeof Rules[key] !== 'function' || !Rules[key](file, rules[key]));
58
47
  cb(null, result);
59
48
  };
60
49
  }
61
- const isUploadAction = ctx => {
62
- var _collection$options;
63
- const _ctx$action = ctx.action,
64
- resourceName = _ctx$action.resourceName,
65
- actionName = _ctx$action.actionName;
66
- if (actionName === 'upload' && resourceName === 'attachments') {
67
- return true;
50
+ function getFileData(ctx) {
51
+ const file = ctx[_constants.FILE_FIELD_NAME],
52
+ storage = ctx.storage;
53
+ if (!file) {
54
+ return ctx.throw(400, 'file validation failed');
68
55
  }
69
- const collection = ctx.db.getCollection(resourceName);
70
- if ((collection === null || collection === void 0 ? void 0 : (_collection$options = collection.options) === null || _collection$options === void 0 ? void 0 : _collection$options.template) === 'file' && ['upload', 'create'].includes(actionName)) {
71
- return true;
72
- }
73
- };
74
- function middleware(_x, _x2) {
75
- return _middleware.apply(this, arguments);
56
+ const storageConfig = (0, _storages.getStorageConfig)(storage.type);
57
+ const name = file[storageConfig.filenameKey || 'filename'];
58
+ // make compatible filename across cloud service (with path)
59
+ const filename = _path().default.basename(name);
60
+ const extname = _path().default.extname(filename);
61
+ const urlPath = storage.path ? storage.path.replace(/^([^/])/, '/$1') : '';
62
+ return _objectSpread({
63
+ title: file.originalname.replace(extname, ''),
64
+ filename,
65
+ extname,
66
+ // TODO(feature): 暂时两者相同,后面 storage.path 模版化以后,这里只是 file 实际的 path
67
+ path: storage.path,
68
+ size: file.size,
69
+ // 直接缓存起来
70
+ url: `${storage.baseUrl}${urlPath}/${filename}`,
71
+ mimetype: file.mimetype,
72
+ // @ts-ignore
73
+ meta: ctx.request.body,
74
+ storageId: storage.id
75
+ }, storageConfig.getFileData ? storageConfig.getFileData(file) : {});
76
76
  }
77
- function _middleware() {
78
- _middleware = _asyncToGenerator(function* (ctx, next) {
79
- const resourceName = ctx.action.resourceName;
80
- const collection = ctx.db.getCollection(resourceName);
81
- if (!isUploadAction(ctx)) {
82
- return next();
83
- }
84
- const Storage = ctx.db.getCollection('storages');
85
- let storage;
86
- if (collection.options.storage) {
87
- storage = yield Storage.repository.findOne({
88
- filter: {
89
- name: collection.options.storage
90
- }
91
- });
92
- } else {
93
- storage = yield Storage.repository.findOne({
94
- filter: {
95
- default: true
96
- }
97
- });
98
- }
77
+ function multipart(_x, _x2) {
78
+ return _multipart.apply(this, arguments);
79
+ }
80
+ function _multipart() {
81
+ _multipart = _asyncToGenerator(function* (ctx, next) {
82
+ var _storage$rules$size;
83
+ const storage = ctx.storage;
99
84
  if (!storage) {
100
- console.error('[file-manager] no default or linked storage provided');
85
+ ctx.logger.error('[file-manager] no linked or default storage provided');
101
86
  return ctx.throw(500);
102
87
  }
103
- // 传递已取得的存储引擎,避免重查
104
- ctx.storage = storage;
105
88
  const storageConfig = (0, _storages.getStorageConfig)(storage.type);
106
89
  if (!storageConfig) {
107
- console.error(`[file-manager] storage type "${storage.type}" is not defined`);
90
+ ctx.logger.error(`[file-manager] storage type "${storage.type}" is not defined`);
108
91
  return ctx.throw(500);
109
92
  }
110
93
  const multerOptions = {
111
- fileFilter: getFileFilter(ctx),
94
+ fileFilter: getFileFilter(storage),
112
95
  limits: {
113
- fileSize: Math.min(getRules(ctx).size || _constants.LIMIT_MAX_FILE_SIZE, _constants.LIMIT_MAX_FILE_SIZE),
96
+ fileSize: (_storage$rules$size = storage.rules.size) !== null && _storage$rules$size !== void 0 ? _storage$rules$size : _constants.DEFAULT_MAX_FILE_SIZE,
114
97
  // 每次只允许提交一个文件
115
98
  files: _constants.LIMIT_FILES
116
99
  },
117
100
  storage: storageConfig.make(storage)
118
101
  };
119
102
  const upload = (0, _multer().default)(multerOptions).single(_constants.FILE_FIELD_NAME);
120
- return upload(ctx, next);
103
+ try {
104
+ // NOTE: empty next and invoke after success
105
+ yield upload(ctx, () => {});
106
+ } catch (err) {
107
+ if (err.name === 'MulterError') {
108
+ return ctx.throw(400, err);
109
+ }
110
+ return ctx.throw(500);
111
+ }
112
+ const values = getFileData(ctx);
113
+ ctx.action.mergeParams({
114
+ values
115
+ });
116
+ yield next();
121
117
  });
122
- return _middleware.apply(this, arguments);
118
+ return _multipart.apply(this, arguments);
123
119
  }
124
- function createAction(_x3, _x4) {
125
- return _createAction.apply(this, arguments);
120
+ function createMiddleware(_x3, _x4) {
121
+ return _createMiddleware.apply(this, arguments);
126
122
  }
127
- function _createAction() {
128
- _createAction = _asyncToGenerator(function* (ctx, next) {
129
- if (!isUploadAction(ctx)) {
123
+ function _createMiddleware() {
124
+ _createMiddleware = _asyncToGenerator(function* (ctx, next) {
125
+ var _collection$options, _ctx$db$getFieldByPat, _ctx$db$getFieldByPat2;
126
+ const _ctx$action = ctx.action,
127
+ resourceName = _ctx$action.resourceName,
128
+ actionName = _ctx$action.actionName;
129
+ const attachmentField = ctx.action.params.attachmentField;
130
+ const collection = ctx.db.getCollection(resourceName);
131
+ if ((collection === null || collection === void 0 ? void 0 : (_collection$options = collection.options) === null || _collection$options === void 0 ? void 0 : _collection$options.template) !== 'file' || !['upload', 'create'].includes(actionName)) {
130
132
  return next();
131
133
  }
132
- const file = ctx[_constants.FILE_FIELD_NAME],
133
- storage = ctx.storage;
134
- if (!file) {
135
- return ctx.throw(400, 'file validation failed');
136
- }
137
- const storageConfig = (0, _storages.getStorageConfig)(storage.type);
138
- const name = file[storageConfig.filenameKey || 'filename'];
139
- // make compatible filename across cloud service (with path)
140
- const filename = _path().default.basename(name);
141
- const extname = _path().default.extname(filename);
142
- const urlPath = storage.path ? storage.path.replace(/^([^\/])/, '/$1') : '';
143
- const values = _objectSpread({
144
- title: file.originalname.replace(extname, ''),
145
- filename,
146
- extname,
147
- // TODO(feature): 暂时两者相同,后面 storage.path 模版化以后,这里只是 file 实际的 path
148
- path: storage.path,
149
- size: file.size,
150
- // 直接缓存起来
151
- url: `${storage.baseUrl}${urlPath}/${filename}`,
152
- mimetype: file.mimetype,
153
- storageId: storage.id,
154
- // @ts-ignore
155
- meta: ctx.request.body
156
- }, storageConfig.getFileData ? storageConfig.getFileData(file) : {});
157
- ctx.action.mergeParams({
158
- values
134
+ const storageName = ((_ctx$db$getFieldByPat = ctx.db.getFieldByPath(attachmentField)) === null || _ctx$db$getFieldByPat === void 0 ? void 0 : (_ctx$db$getFieldByPat2 = _ctx$db$getFieldByPat.options) === null || _ctx$db$getFieldByPat2 === void 0 ? void 0 : _ctx$db$getFieldByPat2.storage) || collection.options.storage;
135
+ const StorageRepo = ctx.db.getRepository('storages');
136
+ const storage = yield StorageRepo.findOne({
137
+ filter: storageName ? {
138
+ name: storageName
139
+ } : {
140
+ default: true
141
+ }
159
142
  });
160
- yield next();
143
+ ctx.storage = storage;
144
+ yield multipart(ctx, next);
161
145
  });
162
- return _createAction.apply(this, arguments);
146
+ return _createMiddleware.apply(this, arguments);
163
147
  }
164
- function uploadAction(_x5, _x6) {
165
- return _uploadAction.apply(this, arguments);
148
+ function destroyMiddleware(_x5, _x6) {
149
+ return _destroyMiddleware.apply(this, arguments);
166
150
  }
167
- function _uploadAction() {
168
- _uploadAction = _asyncToGenerator(function* (ctx, next) {
169
- const file = ctx[_constants.FILE_FIELD_NAME],
170
- storage = ctx.storage;
171
- if (!file) {
172
- return ctx.throw(400, 'file validation failed');
151
+ function _destroyMiddleware() {
152
+ _destroyMiddleware = _asyncToGenerator(function* (ctx, next) {
153
+ var _collection$options2;
154
+ const _ctx$action2 = ctx.action,
155
+ resourceName = _ctx$action2.resourceName,
156
+ actionName = _ctx$action2.actionName;
157
+ const collection = ctx.db.getCollection(resourceName);
158
+ if ((collection === null || collection === void 0 ? void 0 : (_collection$options2 = collection.options) === null || _collection$options2 === void 0 ? void 0 : _collection$options2.template) !== 'file' || actionName !== 'destroy') {
159
+ return next();
173
160
  }
174
- const storageConfig = (0, _storages.getStorageConfig)(storage.type);
175
- const name = file[storageConfig.filenameKey || 'filename'];
176
- // make compatible filename across cloud service (with path)
177
- const filename = _path().default.basename(name);
178
- const extname = _path().default.extname(filename);
179
- const urlPath = storage.path ? storage.path.replace(/^([^\/])/, '/$1') : '';
180
- const data = _objectSpread({
181
- title: file.originalname.replace(extname, ''),
182
- filename,
183
- extname,
184
- // TODO(feature): 暂时两者相同,后面 storage.path 模版化以后,这里只是 file 实际的 path
185
- path: storage.path,
186
- size: file.size,
187
- // 直接缓存起来
188
- url: `${storage.baseUrl}${urlPath}/${filename}`,
189
- mimetype: file.mimetype,
190
- storageId: storage.id,
191
- // @ts-ignore
192
- meta: ctx.request.body
193
- }, storageConfig.getFileData ? storageConfig.getFileData(file) : {});
194
- const fileData = yield ctx.db.sequelize.transaction( /*#__PURE__*/function () {
195
- var _ref2 = _asyncToGenerator(function* (transaction) {
196
- const resourceName = ctx.action.resourceName;
197
- const repository = ctx.db.getRepository(resourceName);
198
- const result = yield repository.create({
199
- values: _objectSpread({}, data),
200
- transaction
201
- });
202
- return result;
161
+ const repository = ctx.db.getRepository(resourceName);
162
+ const _ctx$action$params = ctx.action.params,
163
+ filterByTk = _ctx$action$params.filterByTk,
164
+ filter = _ctx$action$params.filter;
165
+ const records = yield repository.find({
166
+ filterByTk,
167
+ filter,
168
+ context: ctx
169
+ });
170
+ const storageIds = new Set(records.map(record => record.storageId));
171
+ const storageGroupedRecords = records.reduce((result, record) => {
172
+ const storageId = record.storageId;
173
+ if (!result[storageId]) {
174
+ result[storageId] = [];
175
+ }
176
+ result[storageId].push(record);
177
+ return result;
178
+ }, {});
179
+ const storages = yield ctx.db.getRepository('storages').find({
180
+ filter: {
181
+ id: [...storageIds],
182
+ paranoid: {
183
+ $ne: true
184
+ }
185
+ }
186
+ });
187
+ let count = 0;
188
+ const undeleted = [];
189
+ yield storages.reduce((promise, storage) => promise.then( /*#__PURE__*/_asyncToGenerator(function* () {
190
+ const storageConfig = (0, _storages.getStorageConfig)(storage.type);
191
+ const result = yield storageConfig.delete(storage, storageGroupedRecords[storage.id]);
192
+ count += result[0];
193
+ undeleted.push(...result[1]);
194
+ })), Promise.resolve());
195
+ if (undeleted.length) {
196
+ const ids = undeleted.map(record => record.id);
197
+ ctx.action.mergeParams({
198
+ filter: {
199
+ id: {
200
+ $notIn: ids
201
+ }
202
+ }
203
203
  });
204
- return function (_x7) {
205
- return _ref2.apply(this, arguments);
206
- };
207
- }());
208
- ctx.body = fileData;
204
+ ctx.logger.error('[file-manager] some of attachment files are not successfully deleted: ', {
205
+ ids
206
+ });
207
+ }
209
208
  yield next();
210
209
  });
211
- return _uploadAction.apply(this, arguments);
210
+ return _destroyMiddleware.apply(this, arguments);
212
211
  }
@@ -0,0 +1,3 @@
1
+ export default function ({ app }: {
2
+ app: any;
3
+ }): void;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = _default;
7
+ function _actions() {
8
+ const data = _interopRequireDefault(require("@nocobase/actions"));
9
+ _actions = function _actions() {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
14
+ var _attachments = require("./attachments");
15
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
+ function _default({
17
+ app
18
+ }) {
19
+ app.resourcer.use(_attachments.createMiddleware);
20
+ app.resourcer.registerActionHandler('upload', _actions().default.create);
21
+ app.resourcer.use(_attachments.destroyMiddleware);
22
+ }
@@ -11,6 +11,7 @@ var _default = {
11
11
  title: '文件管理器',
12
12
  createdBy: true,
13
13
  updatedBy: true,
14
+ template: 'file',
14
15
  fields: [{
15
16
  comment: '用户文件名(不含扩展名)',
16
17
  type: 'string',
@@ -52,8 +52,9 @@ var _default = {
52
52
  name: 'default',
53
53
  defaultValue: false
54
54
  }, {
55
- type: 'hasMany',
56
- name: 'attachments'
55
+ type: 'boolean',
56
+ name: 'paranoid',
57
+ defaultValue: false
57
58
  }]
58
59
  };
59
60
  exports.default = _default;
@@ -1,6 +1,6 @@
1
1
  export declare const FILE_FIELD_NAME = "file";
2
2
  export declare const LIMIT_FILES = 1;
3
- export declare const LIMIT_MAX_FILE_SIZE: number;
3
+ export declare const DEFAULT_MAX_FILE_SIZE: number;
4
4
  export declare const STORAGE_TYPE_LOCAL = "local";
5
5
  export declare const STORAGE_TYPE_ALI_OSS = "ali-oss";
6
6
  export declare const STORAGE_TYPE_S3 = "s3";
@@ -3,13 +3,13 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.STORAGE_TYPE_TX_COS = exports.STORAGE_TYPE_S3 = exports.STORAGE_TYPE_LOCAL = exports.STORAGE_TYPE_ALI_OSS = exports.LIMIT_MAX_FILE_SIZE = exports.LIMIT_FILES = exports.FILE_FIELD_NAME = void 0;
6
+ exports.STORAGE_TYPE_TX_COS = exports.STORAGE_TYPE_S3 = exports.STORAGE_TYPE_LOCAL = exports.STORAGE_TYPE_ALI_OSS = exports.LIMIT_FILES = exports.FILE_FIELD_NAME = exports.DEFAULT_MAX_FILE_SIZE = void 0;
7
7
  const FILE_FIELD_NAME = 'file';
8
8
  exports.FILE_FIELD_NAME = FILE_FIELD_NAME;
9
9
  const LIMIT_FILES = 1;
10
10
  exports.LIMIT_FILES = LIMIT_FILES;
11
- const LIMIT_MAX_FILE_SIZE = 1024 * 1024 * 1024;
12
- exports.LIMIT_MAX_FILE_SIZE = LIMIT_MAX_FILE_SIZE;
11
+ const DEFAULT_MAX_FILE_SIZE = 1024 * 1024 * 1024;
12
+ exports.DEFAULT_MAX_FILE_SIZE = DEFAULT_MAX_FILE_SIZE;
13
13
  const STORAGE_TYPE_LOCAL = 'local';
14
14
  exports.STORAGE_TYPE_LOCAL = STORAGE_TYPE_LOCAL;
15
15
  const STORAGE_TYPE_ALI_OSS = 'ali-oss';
@@ -1 +1 @@
1
- export default function (file: any, options: string | string[], ctx: any): boolean;
1
+ export default function (file: any, options?: string | string[]): boolean;