@webiny/api-file-manager-s3 0.0.0-mt-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/index.d.ts +2 -0
- package/index.js +16 -0
- package/package.json +38 -0
- package/plugins/fileStorageS3.d.ts +3 -0
- package/plugins/fileStorageS3.js +59 -0
- package/plugins/graphqlFileStorageS3.d.ts +4 -0
- package/plugins/graphqlFileStorageS3.js +135 -0
- package/types.d.ts +0 -0
- package/types.js +1 -0
- package/utils/getPresignedPostPayload.d.ts +11 -0
- package/utils/getPresignedPostPayload.js +86 -0
- package/utils/uploadFileToS3.d.ts +2 -0
- package/utils/uploadFileToS3.js +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Webiny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @webiny/api-file-manager-s3
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: () => (import("@webiny/handler-graphql/types").GraphQLSchemaPlugin<import("@webiny/api-file-manager/types").FileManagerContext> | import("@webiny/api-file-manager/plugins/definitions/FilePhysicalStoragePlugin").FilePhysicalStoragePlugin)[];
|
|
2
|
+
export default _default;
|
package/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = void 0;
|
|
9
|
+
|
|
10
|
+
var _graphqlFileStorageS = _interopRequireDefault(require("./plugins/graphqlFileStorageS3"));
|
|
11
|
+
|
|
12
|
+
var _fileStorageS = _interopRequireDefault(require("./plugins/fileStorageS3"));
|
|
13
|
+
|
|
14
|
+
var _default = () => [(0, _fileStorageS.default)(), _graphqlFileStorageS.default];
|
|
15
|
+
|
|
16
|
+
exports.default = _default;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webiny/api-file-manager-s3",
|
|
3
|
+
"version": "0.0.0-mt-1",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/webiny/webiny-js.git"
|
|
8
|
+
},
|
|
9
|
+
"description": "File storage S3 plugin for the @webiny/api-file-manager.",
|
|
10
|
+
"author": "Webiny Ltd",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@webiny/api-file-manager": "0.0.0-mt-1",
|
|
14
|
+
"@webiny/handler-graphql": "0.0.0-mt-1",
|
|
15
|
+
"@webiny/validation": "0.0.0-mt-1",
|
|
16
|
+
"form-data": "3.0.1",
|
|
17
|
+
"node-fetch": "2.6.5",
|
|
18
|
+
"sanitize-filename": "1.6.3",
|
|
19
|
+
"uniqid": "5.4.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@babel/cli": "^7.5.5",
|
|
23
|
+
"@babel/core": "^7.5.5",
|
|
24
|
+
"@webiny/cli": "^0.0.0-mt-1",
|
|
25
|
+
"@webiny/project-utils": "^0.0.0-mt-1",
|
|
26
|
+
"rimraf": "^3.0.2",
|
|
27
|
+
"typescript": "^4.1.3"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"directory": "dist"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "yarn webiny run build",
|
|
35
|
+
"watch": "yarn webiny run watch"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "37736d8456a6ecb342a6c3645060bd9a3f2d4bb0"
|
|
38
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = void 0;
|
|
9
|
+
|
|
10
|
+
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
|
|
11
|
+
|
|
12
|
+
var _s = _interopRequireDefault(require("aws-sdk/clients/s3"));
|
|
13
|
+
|
|
14
|
+
var _getPresignedPostPayload = _interopRequireDefault(require("../utils/getPresignedPostPayload"));
|
|
15
|
+
|
|
16
|
+
var _uploadFileToS = _interopRequireDefault(require("../utils/uploadFileToS3"));
|
|
17
|
+
|
|
18
|
+
var _FilePhysicalStoragePlugin = require("@webiny/api-file-manager/plugins/definitions/FilePhysicalStoragePlugin");
|
|
19
|
+
|
|
20
|
+
const _excluded = ["settings", "buffer"];
|
|
21
|
+
const S3_BUCKET = process.env.S3_BUCKET;
|
|
22
|
+
|
|
23
|
+
var _default = () => {
|
|
24
|
+
return new _FilePhysicalStoragePlugin.FilePhysicalStoragePlugin({
|
|
25
|
+
upload: async args => {
|
|
26
|
+
const {
|
|
27
|
+
settings,
|
|
28
|
+
buffer
|
|
29
|
+
} = args,
|
|
30
|
+
data = (0, _objectWithoutProperties2.default)(args, _excluded);
|
|
31
|
+
const {
|
|
32
|
+
data: preSignedPostPayload,
|
|
33
|
+
file
|
|
34
|
+
} = await (0, _getPresignedPostPayload.default)(data, settings);
|
|
35
|
+
const response = await (0, _uploadFileToS.default)(buffer, preSignedPostPayload);
|
|
36
|
+
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw Error("Unable to upload file.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
data: preSignedPostPayload,
|
|
43
|
+
file
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
delete: async args => {
|
|
47
|
+
const {
|
|
48
|
+
key
|
|
49
|
+
} = args;
|
|
50
|
+
const s3 = new _s.default();
|
|
51
|
+
await s3.deleteObject({
|
|
52
|
+
Bucket: S3_BUCKET,
|
|
53
|
+
Key: key
|
|
54
|
+
}).promise();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
exports.default = _default;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = void 0;
|
|
9
|
+
|
|
10
|
+
var _responses = require("@webiny/handler-graphql/responses");
|
|
11
|
+
|
|
12
|
+
var _checkBasePermissions = _interopRequireDefault(require("@webiny/api-file-manager/plugins/crud/utils/checkBasePermissions"));
|
|
13
|
+
|
|
14
|
+
var _getPresignedPostPayload = _interopRequireDefault(require("../utils/getPresignedPostPayload"));
|
|
15
|
+
|
|
16
|
+
const BATCH_UPLOAD_MAX_FILES = 20;
|
|
17
|
+
const plugin = {
|
|
18
|
+
type: "graphql-schema",
|
|
19
|
+
name: "graphql-schema-api-file-manager-s3",
|
|
20
|
+
schema: {
|
|
21
|
+
typeDefs:
|
|
22
|
+
/* GraphQL */
|
|
23
|
+
`
|
|
24
|
+
input PreSignedPostPayloadInput {
|
|
25
|
+
name: String!
|
|
26
|
+
type: String!
|
|
27
|
+
size: Int!
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type GetPreSignedPostPayloadResponseDataFile {
|
|
31
|
+
name: String
|
|
32
|
+
type: String
|
|
33
|
+
size: Int
|
|
34
|
+
key: String
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type GetPreSignedPostPayloadResponseData {
|
|
38
|
+
# Contains data that is necessary for initiating a file upload.
|
|
39
|
+
data: JSON
|
|
40
|
+
file: UploadFileResponseDataFile
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type GetPreSignedPostPayloadResponse {
|
|
44
|
+
error: FileError
|
|
45
|
+
data: GetPreSignedPostPayloadResponseData
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type GetPreSignedPostPayloadsResponse {
|
|
49
|
+
error: FileError
|
|
50
|
+
data: [GetPreSignedPostPayloadResponseData]!
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
extend type FmQuery {
|
|
54
|
+
getPreSignedPostPayload(
|
|
55
|
+
data: PreSignedPostPayloadInput!
|
|
56
|
+
): GetPreSignedPostPayloadResponse
|
|
57
|
+
getPreSignedPostPayloads(
|
|
58
|
+
data: [PreSignedPostPayloadInput]!
|
|
59
|
+
): GetPreSignedPostPayloadsResponse
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
resolvers: {
|
|
63
|
+
FmQuery: {
|
|
64
|
+
getPreSignedPostPayload: async (_, args, context) => {
|
|
65
|
+
try {
|
|
66
|
+
await (0, _checkBasePermissions.default)(context, {
|
|
67
|
+
rwd: "w"
|
|
68
|
+
});
|
|
69
|
+
const {
|
|
70
|
+
data
|
|
71
|
+
} = args;
|
|
72
|
+
const settings = await context.fileManager.settings.getSettings();
|
|
73
|
+
const response = await (0, _getPresignedPostPayload.default)(data, settings);
|
|
74
|
+
return new _responses.Response(response);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
return new _responses.ErrorResponse({
|
|
77
|
+
message: e.message,
|
|
78
|
+
code: e.code,
|
|
79
|
+
data: e.data
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
getPreSignedPostPayloads: async (_, args, context) => {
|
|
84
|
+
await (0, _checkBasePermissions.default)(context, {
|
|
85
|
+
rwd: "w"
|
|
86
|
+
});
|
|
87
|
+
const {
|
|
88
|
+
data: files
|
|
89
|
+
} = args;
|
|
90
|
+
|
|
91
|
+
if (!Array.isArray(files)) {
|
|
92
|
+
return new _responses.ErrorResponse({
|
|
93
|
+
code: "UPLOAD_FILES_NON_ARRAY",
|
|
94
|
+
message: `"data" argument must be an array.`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (files.length === 0) {
|
|
99
|
+
return new _responses.ErrorResponse({
|
|
100
|
+
code: "UPLOAD_FILES_MIN_FILES",
|
|
101
|
+
message: `"data" argument must contain at least one file.`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (files.length > BATCH_UPLOAD_MAX_FILES) {
|
|
106
|
+
return new _responses.ErrorResponse({
|
|
107
|
+
code: "UPLOAD_FILES_MAX_FILES",
|
|
108
|
+
message: `"data" argument must not contain more than ${BATCH_UPLOAD_MAX_FILES} files.`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const settings = await context.fileManager.settings.getSettings();
|
|
114
|
+
const promises = [];
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < files.length; i++) {
|
|
117
|
+
const item = files[i];
|
|
118
|
+
promises.push((0, _getPresignedPostPayload.default)(item, settings));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return new _responses.Response(await Promise.all(promises));
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return new _responses.ErrorResponse({
|
|
124
|
+
message: e.message,
|
|
125
|
+
code: e.code,
|
|
126
|
+
data: e.data
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var _default = plugin;
|
|
135
|
+
exports.default = _default;
|
package/types.d.ts
ADDED
|
File without changes
|
package/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = void 0;
|
|
9
|
+
|
|
10
|
+
var _uniqid = _interopRequireDefault(require("uniqid"));
|
|
11
|
+
|
|
12
|
+
var _sanitizeFilename = _interopRequireDefault(require("sanitize-filename"));
|
|
13
|
+
|
|
14
|
+
var _s = _interopRequireDefault(require("aws-sdk/clients/s3"));
|
|
15
|
+
|
|
16
|
+
var _validation = require("@webiny/validation");
|
|
17
|
+
|
|
18
|
+
const S3_BUCKET = process.env.S3_BUCKET;
|
|
19
|
+
const UPLOAD_MAX_FILE_SIZE_DEFAULT = 26214400; // 25MB
|
|
20
|
+
|
|
21
|
+
const sanitizeFileSizeValue = (value, defaultValue) => {
|
|
22
|
+
try {
|
|
23
|
+
_validation.validation.validateSync(value, "required,numeric,gte:0");
|
|
24
|
+
|
|
25
|
+
return value;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return defaultValue;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
var _default = async (data, settings) => {
|
|
32
|
+
// If type is missing, let's use the default "application/octet-stream" type,
|
|
33
|
+
// which is also the default type that the Amazon S3 would use.
|
|
34
|
+
if (!data.type) {
|
|
35
|
+
data.type = "application/octet-stream";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const contentType = data.type;
|
|
39
|
+
|
|
40
|
+
if (!contentType) {
|
|
41
|
+
throw Error(`File's content type could not be resolved.`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let key = (0, _sanitizeFilename.default)(data.name);
|
|
45
|
+
|
|
46
|
+
if (key) {
|
|
47
|
+
key = (0, _uniqid.default)() + "-" + key;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (data.keyPrefix) {
|
|
51
|
+
key = `${(0, _sanitizeFilename.default)(data.keyPrefix)}-${key}`;
|
|
52
|
+
} // Replace all whitespace.
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
key = key.replace(/\s/g, "");
|
|
56
|
+
const uploadMinFileSize = sanitizeFileSizeValue(settings.uploadMinFileSize, 0);
|
|
57
|
+
const uploadMaxFileSize = sanitizeFileSizeValue(settings.uploadMaxFileSize, UPLOAD_MAX_FILE_SIZE_DEFAULT);
|
|
58
|
+
const params = {
|
|
59
|
+
Expires: 60,
|
|
60
|
+
Bucket: S3_BUCKET,
|
|
61
|
+
Conditions: [["content-length-range", uploadMinFileSize, uploadMaxFileSize]],
|
|
62
|
+
// 0 Bytes - 25MB
|
|
63
|
+
Fields: {
|
|
64
|
+
"Content-Type": contentType,
|
|
65
|
+
key
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (params.Fields.key.startsWith("/")) {
|
|
70
|
+
params.Fields.key = params.Fields.key.substr(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const s3 = new _s.default();
|
|
74
|
+
const payload = s3.createPresignedPost(params);
|
|
75
|
+
return {
|
|
76
|
+
data: payload,
|
|
77
|
+
file: {
|
|
78
|
+
name: key,
|
|
79
|
+
key,
|
|
80
|
+
type: contentType,
|
|
81
|
+
size: data.size
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
exports.default = _default;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.default = void 0;
|
|
9
|
+
|
|
10
|
+
var _formData = _interopRequireDefault(require("form-data"));
|
|
11
|
+
|
|
12
|
+
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
13
|
+
|
|
14
|
+
var _default = async (buffer, preSignedPostPayload) => {
|
|
15
|
+
const formData = new _formData.default(); // Add all pre signed payload field to "FormData".
|
|
16
|
+
|
|
17
|
+
Object.keys(preSignedPostPayload.fields).forEach(key => {
|
|
18
|
+
formData.append(key, preSignedPostPayload.fields[key]);
|
|
19
|
+
}); // Add file content to "FormData".
|
|
20
|
+
|
|
21
|
+
formData.append("file", buffer); // Finally make the upload request to S3.
|
|
22
|
+
|
|
23
|
+
return (0, _nodeFetch.default)(preSignedPostPayload.url, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: formData
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
exports.default = _default;
|