next-tinacms-s3 5.0.9 → 7.0.0
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/handlers.d.ts +2 -1
- package/dist/handlers.js +51 -66
- package/dist/index.js +45 -11
- package/dist/index.mjs +45 -11
- package/package.json +6 -6
package/dist/handlers.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
|
|
3
3
|
*/
|
|
4
|
-
import { S3ClientConfig } from '@aws-sdk/client-s3';
|
|
4
|
+
import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3';
|
|
5
5
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
6
6
|
export interface S3Config {
|
|
7
7
|
config: S3ClientConfig;
|
|
@@ -18,3 +18,4 @@ export declare const mediaHandlerConfig: {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
export declare const createMediaHandler: (config: S3Config, options?: S3Options) => (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
|
|
21
|
+
export declare const getUploadUrl: (bucket: string, key: string, expiresIn: number, client: S3Client) => Promise<string>;
|
package/dist/handlers.js
CHANGED
|
@@ -26,14 +26,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
26
26
|
var handlers_exports = {};
|
|
27
27
|
__export(handlers_exports, {
|
|
28
28
|
createMediaHandler: () => createMediaHandler,
|
|
29
|
+
getUploadUrl: () => getUploadUrl,
|
|
29
30
|
mediaHandlerConfig: () => mediaHandlerConfig
|
|
30
31
|
});
|
|
31
32
|
module.exports = __toCommonJS(handlers_exports);
|
|
32
33
|
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
34
|
+
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
33
35
|
var import_path = __toESM(require("path"));
|
|
34
|
-
var import_fs = __toESM(require("fs"));
|
|
35
|
-
var import_multer = __toESM(require("multer"));
|
|
36
|
-
var import_util = require("util");
|
|
37
36
|
var mediaHandlerConfig = {
|
|
38
37
|
api: {
|
|
39
38
|
bodyParser: false
|
|
@@ -63,9 +62,25 @@ var createMediaHandler = (config, options) => {
|
|
|
63
62
|
}
|
|
64
63
|
switch (req.method) {
|
|
65
64
|
case "GET":
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
if (req.url.startsWith("/api/s3/media/upload_url")) {
|
|
66
|
+
const expiresIn = req.query.expiresIn && Number(req.query.expiresIn) || 3600;
|
|
67
|
+
const s3_key = req.query.key ? Array.isArray(req.query.key) ? req.query.key[0] : req.query.key : null;
|
|
68
|
+
if (!s3_key) {
|
|
69
|
+
return res.status(400).json({ message: "key is required" });
|
|
70
|
+
}
|
|
71
|
+
if (await keyExists(client, bucket, s3_key)) {
|
|
72
|
+
return res.status(400).json({ message: "key already exists" });
|
|
73
|
+
}
|
|
74
|
+
const signedUrl = await getUploadUrl(
|
|
75
|
+
bucket,
|
|
76
|
+
s3_key,
|
|
77
|
+
expiresIn,
|
|
78
|
+
client
|
|
79
|
+
);
|
|
80
|
+
return res.json({ signedUrl, src: cdnUrl + s3_key });
|
|
81
|
+
} else {
|
|
82
|
+
return listMedia(req, res, client, bucket, mediaRoot, cdnUrl);
|
|
83
|
+
}
|
|
69
84
|
case "DELETE":
|
|
70
85
|
return deleteAsset(req, res, client, bucket);
|
|
71
86
|
default:
|
|
@@ -73,66 +88,6 @@ var createMediaHandler = (config, options) => {
|
|
|
73
88
|
}
|
|
74
89
|
};
|
|
75
90
|
};
|
|
76
|
-
async function uploadMedia(req, res, client, bucket, mediaRoot, cdnUrl) {
|
|
77
|
-
var _a;
|
|
78
|
-
try {
|
|
79
|
-
const upload = (0, import_util.promisify)(
|
|
80
|
-
(0, import_multer.default)({
|
|
81
|
-
storage: import_multer.default.diskStorage({
|
|
82
|
-
directory: (req2, file, cb) => {
|
|
83
|
-
cb(null, "/tmp");
|
|
84
|
-
},
|
|
85
|
-
filename: (req2, file, cb) => {
|
|
86
|
-
cb(null, file.originalname);
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
}).single("file")
|
|
90
|
-
);
|
|
91
|
-
await upload(req, res);
|
|
92
|
-
const { directory } = req.body;
|
|
93
|
-
let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
|
|
94
|
-
if (prefix)
|
|
95
|
-
prefix = prefix + "/";
|
|
96
|
-
const filePath = req.file.path;
|
|
97
|
-
const fileType = (_a = req.file) == null ? void 0 : _a.mimetype;
|
|
98
|
-
const blob = import_fs.default.readFileSync(filePath);
|
|
99
|
-
const filename = import_path.default.basename(filePath);
|
|
100
|
-
const params = {
|
|
101
|
-
Bucket: bucket,
|
|
102
|
-
Key: mediaRoot ? import_path.default.join(mediaRoot, prefix + filename) : prefix + filename,
|
|
103
|
-
Body: blob,
|
|
104
|
-
ACL: "public-read",
|
|
105
|
-
ContentType: fileType || "application/octet-stream"
|
|
106
|
-
};
|
|
107
|
-
const command = new import_client_s3.PutObjectCommand(params);
|
|
108
|
-
try {
|
|
109
|
-
await client.send(command);
|
|
110
|
-
const src = cdnUrl + prefix + filename;
|
|
111
|
-
res.json({
|
|
112
|
-
type: "file",
|
|
113
|
-
id: prefix + filename,
|
|
114
|
-
filename,
|
|
115
|
-
directory: prefix,
|
|
116
|
-
thumbnails: {
|
|
117
|
-
"75x75": src,
|
|
118
|
-
"400x400": src,
|
|
119
|
-
"1000x1000": src
|
|
120
|
-
},
|
|
121
|
-
src: cdnUrl + (mediaRoot ? import_path.default.join(mediaRoot, prefix + filename) : prefix + filename)
|
|
122
|
-
});
|
|
123
|
-
} catch (e) {
|
|
124
|
-
console.error("Error uploading media to s3");
|
|
125
|
-
console.error(e);
|
|
126
|
-
res.status(500).send(findErrorMessage(e));
|
|
127
|
-
}
|
|
128
|
-
} catch (e) {
|
|
129
|
-
console.error("Error uploading media");
|
|
130
|
-
console.error(e);
|
|
131
|
-
res.status(500);
|
|
132
|
-
const message = findErrorMessage(e);
|
|
133
|
-
res.json({ e: message });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
91
|
function stripMediaRoot(mediaRoot, key) {
|
|
137
92
|
if (!mediaRoot) {
|
|
138
93
|
return key;
|
|
@@ -228,6 +183,35 @@ async function deleteAsset(req, res, client, bucket) {
|
|
|
228
183
|
res.json({ e: message });
|
|
229
184
|
}
|
|
230
185
|
}
|
|
186
|
+
async function keyExists(client, bucket, key) {
|
|
187
|
+
var _a, _b;
|
|
188
|
+
try {
|
|
189
|
+
const cmd = new import_client_s3.HeadObjectCommand({
|
|
190
|
+
Bucket: bucket,
|
|
191
|
+
Key: key
|
|
192
|
+
});
|
|
193
|
+
const output = await client.send(cmd);
|
|
194
|
+
return output && output.$metadata.httpStatusCode === 200;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (((_a = error.$metadata) == null ? void 0 : _a.httpStatusCode) === 404) {
|
|
197
|
+
return false;
|
|
198
|
+
} else if (((_b = error.$metadata) == null ? void 0 : _b.httpStatusCode) === 403) {
|
|
199
|
+
return false;
|
|
200
|
+
} else {
|
|
201
|
+
throw new Error("unexpected error checking if key exists");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
var getUploadUrl = async (bucket, key, expiresIn, client) => {
|
|
206
|
+
return (0, import_s3_request_presigner.getSignedUrl)(
|
|
207
|
+
client,
|
|
208
|
+
new import_client_s3.PutObjectCommand({
|
|
209
|
+
Bucket: bucket,
|
|
210
|
+
Key: key
|
|
211
|
+
}),
|
|
212
|
+
{ expiresIn }
|
|
213
|
+
);
|
|
214
|
+
};
|
|
231
215
|
function getS3ToTinaFunc(cdnUrl, mediaRoot) {
|
|
232
216
|
return function s3ToTina(file) {
|
|
233
217
|
const strippedKey = stripMediaRoot(mediaRoot, file.Key);
|
|
@@ -251,5 +235,6 @@ function getS3ToTinaFunc(cdnUrl, mediaRoot) {
|
|
|
251
235
|
// Annotate the CommonJS export names for ESM import in node:
|
|
252
236
|
0 && (module.exports = {
|
|
253
237
|
createMediaHandler,
|
|
238
|
+
getUploadUrl,
|
|
254
239
|
mediaHandlerConfig
|
|
255
240
|
});
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
return E_DEFAULT;
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
+
const s3ErrorRegex = /<Error>.*<Code>(.+)<\/Code>.*<Message>(.+)<\/Message>.*/;
|
|
50
51
|
class S3MediaStore {
|
|
51
52
|
constructor() {
|
|
52
53
|
this.fetchFunction = (input, init) => {
|
|
@@ -60,24 +61,57 @@
|
|
|
60
61
|
async persist(media) {
|
|
61
62
|
const newFiles = [];
|
|
62
63
|
for (const item of media) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const res = await this.fetchFunction(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
let directory = item.directory;
|
|
65
|
+
if (directory == null ? void 0 : directory.endsWith("/")) {
|
|
66
|
+
directory = directory.substr(0, directory.length - 1);
|
|
67
|
+
}
|
|
68
|
+
const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
|
|
69
|
+
const res = await this.fetchFunction(
|
|
70
|
+
`/api/s3/media/upload_url?key=${path}`,
|
|
71
|
+
{
|
|
72
|
+
method: "GET"
|
|
73
|
+
}
|
|
74
|
+
);
|
|
72
75
|
if (res.status != 200) {
|
|
73
76
|
const responseData = await res.json();
|
|
74
77
|
throw new Error(responseData.message);
|
|
75
78
|
}
|
|
76
|
-
const
|
|
79
|
+
const { signedUrl, src } = await res.json();
|
|
80
|
+
if (!signedUrl || !src) {
|
|
81
|
+
throw new Error("Unexpected error generating upload url");
|
|
82
|
+
}
|
|
83
|
+
const uploadRes = await fetch(signedUrl, {
|
|
84
|
+
method: "PUT",
|
|
85
|
+
body: item.file,
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": item.file.type || "application/octet-stream"
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (!uploadRes.ok) {
|
|
91
|
+
const xmlRes = await uploadRes.text();
|
|
92
|
+
const matches = s3ErrorRegex.exec(xmlRes);
|
|
93
|
+
console.error(xmlRes);
|
|
94
|
+
if (!matches) {
|
|
95
|
+
throw new Error("Unexpected error uploading media asset");
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error(`Upload error: '${matches[2]}'`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
77
100
|
await new Promise((resolve) => {
|
|
78
101
|
setTimeout(resolve, 2e3);
|
|
79
102
|
});
|
|
80
|
-
newFiles.push(
|
|
103
|
+
newFiles.push({
|
|
104
|
+
directory: item.directory,
|
|
105
|
+
filename: item.file.name,
|
|
106
|
+
id: item.file.name,
|
|
107
|
+
type: "file",
|
|
108
|
+
thumbnails: {
|
|
109
|
+
"75x75": src,
|
|
110
|
+
"400x400": src,
|
|
111
|
+
"1000x1000": src
|
|
112
|
+
},
|
|
113
|
+
src
|
|
114
|
+
});
|
|
81
115
|
}
|
|
82
116
|
return newFiles;
|
|
83
117
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -44,6 +44,7 @@ const interpretErrorMessage = (message) => {
|
|
|
44
44
|
return E_DEFAULT;
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
+
const s3ErrorRegex = /<Error>.*<Code>(.+)<\/Code>.*<Message>(.+)<\/Message>.*/;
|
|
47
48
|
class S3MediaStore {
|
|
48
49
|
constructor() {
|
|
49
50
|
this.fetchFunction = (input, init) => {
|
|
@@ -57,24 +58,57 @@ class S3MediaStore {
|
|
|
57
58
|
async persist(media) {
|
|
58
59
|
const newFiles = [];
|
|
59
60
|
for (const item of media) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const res = await this.fetchFunction(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
let directory = item.directory;
|
|
62
|
+
if (directory == null ? void 0 : directory.endsWith("/")) {
|
|
63
|
+
directory = directory.substr(0, directory.length - 1);
|
|
64
|
+
}
|
|
65
|
+
const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
|
|
66
|
+
const res = await this.fetchFunction(
|
|
67
|
+
`/api/s3/media/upload_url?key=${path}`,
|
|
68
|
+
{
|
|
69
|
+
method: "GET"
|
|
70
|
+
}
|
|
71
|
+
);
|
|
69
72
|
if (res.status != 200) {
|
|
70
73
|
const responseData = await res.json();
|
|
71
74
|
throw new Error(responseData.message);
|
|
72
75
|
}
|
|
73
|
-
const
|
|
76
|
+
const { signedUrl, src } = await res.json();
|
|
77
|
+
if (!signedUrl || !src) {
|
|
78
|
+
throw new Error("Unexpected error generating upload url");
|
|
79
|
+
}
|
|
80
|
+
const uploadRes = await fetch(signedUrl, {
|
|
81
|
+
method: "PUT",
|
|
82
|
+
body: item.file,
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": item.file.type || "application/octet-stream"
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (!uploadRes.ok) {
|
|
88
|
+
const xmlRes = await uploadRes.text();
|
|
89
|
+
const matches = s3ErrorRegex.exec(xmlRes);
|
|
90
|
+
console.error(xmlRes);
|
|
91
|
+
if (!matches) {
|
|
92
|
+
throw new Error("Unexpected error uploading media asset");
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error(`Upload error: '${matches[2]}'`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
74
97
|
await new Promise((resolve) => {
|
|
75
98
|
setTimeout(resolve, 2e3);
|
|
76
99
|
});
|
|
77
|
-
newFiles.push(
|
|
100
|
+
newFiles.push({
|
|
101
|
+
directory: item.directory,
|
|
102
|
+
filename: item.file.name,
|
|
103
|
+
id: item.file.name,
|
|
104
|
+
type: "file",
|
|
105
|
+
thumbnails: {
|
|
106
|
+
"75x75": src,
|
|
107
|
+
"400x400": src,
|
|
108
|
+
"1000x1000": src
|
|
109
|
+
},
|
|
110
|
+
src
|
|
111
|
+
});
|
|
78
112
|
}
|
|
79
113
|
return newFiles;
|
|
80
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-tinacms-s3",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"files": [
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"react": "^18.3.1",
|
|
27
27
|
"react-dom": "^18.3.1",
|
|
28
28
|
"typescript": "^5.6.2",
|
|
29
|
-
"@tinacms/scripts": "1.
|
|
30
|
-
"tinacms": "2.
|
|
29
|
+
"@tinacms/scripts": "1.3.0",
|
|
30
|
+
"tinacms": "2.4.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"tinacms": "2.
|
|
33
|
+
"tinacms": "2.4.0"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"registry": "https://registry.npmjs.org"
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"directory": "packages/next-tinacms-s3"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@aws-sdk/
|
|
44
|
-
"
|
|
43
|
+
"@aws-sdk/s3-request-presigner": "^3.658.1",
|
|
44
|
+
"@aws-sdk/client-s3": "^3.658.1"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"types": "pnpm tsc",
|