next-tinacms-dos 0.0.0-20240902032558 → 0.0.0-20240903031924
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.mjs +215 -0
- package/package.json +4 -4
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// src/handlers.ts
|
|
2
|
+
import {
|
|
3
|
+
S3Client,
|
|
4
|
+
ListObjectsCommand,
|
|
5
|
+
PutObjectCommand,
|
|
6
|
+
DeleteObjectCommand
|
|
7
|
+
} from "@aws-sdk/client-s3";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import multer from "multer";
|
|
11
|
+
import { promisify } from "util";
|
|
12
|
+
var mediaHandlerConfig = {
|
|
13
|
+
api: {
|
|
14
|
+
bodyParser: false
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var createMediaHandler = (config, options) => {
|
|
18
|
+
const client = new S3Client(config.config);
|
|
19
|
+
const bucket = config.bucket;
|
|
20
|
+
let mediaRoot = config.mediaRoot || "";
|
|
21
|
+
if (mediaRoot) {
|
|
22
|
+
if (!mediaRoot.endsWith("/")) {
|
|
23
|
+
mediaRoot = mediaRoot + "/";
|
|
24
|
+
}
|
|
25
|
+
if (mediaRoot.startsWith("/")) {
|
|
26
|
+
mediaRoot = mediaRoot.substr(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
let cdnUrl = options?.cdnUrl || config.config.endpoint.toString().replace(/http(s|):\/\//i, `https://${bucket}.`);
|
|
30
|
+
cdnUrl = cdnUrl + (cdnUrl.endsWith("/") ? "" : "/");
|
|
31
|
+
return async (req, res) => {
|
|
32
|
+
const isAuthorized = await config.authorized(req, res);
|
|
33
|
+
if (!isAuthorized) {
|
|
34
|
+
res.status(401).json({ message: "sorry this user is unauthorized" });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
switch (req.method) {
|
|
38
|
+
case "GET":
|
|
39
|
+
return listMedia(req, res, client, bucket, mediaRoot, cdnUrl);
|
|
40
|
+
case "POST":
|
|
41
|
+
return uploadMedia(req, res, client, bucket, mediaRoot, cdnUrl);
|
|
42
|
+
case "DELETE":
|
|
43
|
+
return deleteAsset(req, res, client, bucket);
|
|
44
|
+
default:
|
|
45
|
+
res.end(404);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
async function uploadMedia(req, res, client, bucket, mediaRoot, cdnUrl) {
|
|
50
|
+
const upload = promisify(
|
|
51
|
+
multer({
|
|
52
|
+
storage: multer.diskStorage({
|
|
53
|
+
directory: (req2, file, cb) => {
|
|
54
|
+
cb(null, "/tmp");
|
|
55
|
+
},
|
|
56
|
+
filename: (req2, file, cb) => {
|
|
57
|
+
cb(null, file.originalname);
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}).single("file")
|
|
61
|
+
);
|
|
62
|
+
await upload(req, res);
|
|
63
|
+
const { directory } = req.body;
|
|
64
|
+
let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
|
|
65
|
+
if (prefix)
|
|
66
|
+
prefix = prefix + "/";
|
|
67
|
+
const filePath = req.file.path;
|
|
68
|
+
const fileType = req.file?.mimetype;
|
|
69
|
+
const blob = fs.readFileSync(filePath);
|
|
70
|
+
const filename = path.basename(filePath);
|
|
71
|
+
const params = {
|
|
72
|
+
Bucket: bucket,
|
|
73
|
+
Key: mediaRoot ? path.join(mediaRoot, prefix + filename) : prefix + filename,
|
|
74
|
+
Body: blob,
|
|
75
|
+
ACL: "public-read",
|
|
76
|
+
ContentType: fileType || "application/octet-stream"
|
|
77
|
+
};
|
|
78
|
+
const command = new PutObjectCommand(params);
|
|
79
|
+
try {
|
|
80
|
+
const src = cdnUrl + prefix + filename;
|
|
81
|
+
await client.send(command);
|
|
82
|
+
res.json({
|
|
83
|
+
type: "file",
|
|
84
|
+
id: prefix + filename,
|
|
85
|
+
filename,
|
|
86
|
+
directory: prefix,
|
|
87
|
+
thumbnails: {
|
|
88
|
+
"75x75": src,
|
|
89
|
+
"400x400": src,
|
|
90
|
+
"1000x1000": src
|
|
91
|
+
},
|
|
92
|
+
src: cdnUrl + (mediaRoot ? path.join(mediaRoot, prefix + filename) : prefix + filename)
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error("Error uploading media");
|
|
96
|
+
console.error(e);
|
|
97
|
+
res.status(500).send(findErrorMessage(e));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function stripMediaRoot(mediaRoot, key) {
|
|
101
|
+
if (!mediaRoot) {
|
|
102
|
+
return key;
|
|
103
|
+
}
|
|
104
|
+
const mediaRootParts = mediaRoot.split("/").filter((part) => part);
|
|
105
|
+
if (!mediaRootParts || !mediaRootParts[0]) {
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
const keyParts = key.split("/").filter((part) => part);
|
|
109
|
+
for (let i = 0; i < mediaRootParts.length; i++) {
|
|
110
|
+
if (keyParts[0] === mediaRootParts[i]) {
|
|
111
|
+
keyParts.shift();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return keyParts.join("/");
|
|
115
|
+
}
|
|
116
|
+
async function listMedia(req, res, client, bucket, mediaRoot, cdnUrl) {
|
|
117
|
+
try {
|
|
118
|
+
const {
|
|
119
|
+
directory = "",
|
|
120
|
+
limit = 500,
|
|
121
|
+
offset
|
|
122
|
+
} = req.query;
|
|
123
|
+
let prefix = directory.replace(/^\//, "").replace(/\/$/, "");
|
|
124
|
+
if (prefix)
|
|
125
|
+
prefix = prefix + "/";
|
|
126
|
+
const params = {
|
|
127
|
+
Bucket: bucket,
|
|
128
|
+
Delimiter: "/",
|
|
129
|
+
Prefix: mediaRoot ? path.join(mediaRoot, prefix) : prefix,
|
|
130
|
+
Marker: offset?.toString(),
|
|
131
|
+
MaxKeys: directory && !offset ? +limit + 1 : +limit
|
|
132
|
+
};
|
|
133
|
+
const response = await client.send(new ListObjectsCommand(params));
|
|
134
|
+
const items = [];
|
|
135
|
+
response.CommonPrefixes?.forEach(({ Prefix }) => {
|
|
136
|
+
const strippedPrefix = stripMediaRoot(mediaRoot, Prefix);
|
|
137
|
+
if (!strippedPrefix) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
items.push({
|
|
141
|
+
id: Prefix,
|
|
142
|
+
type: "dir",
|
|
143
|
+
filename: path.basename(strippedPrefix),
|
|
144
|
+
directory: path.dirname(strippedPrefix)
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
items.push(
|
|
148
|
+
...(response.Contents || []).filter((file) => {
|
|
149
|
+
const strippedKey = stripMediaRoot(mediaRoot, file.Key);
|
|
150
|
+
return strippedKey !== prefix;
|
|
151
|
+
}).map(getDOSToTinaFunc(cdnUrl, mediaRoot))
|
|
152
|
+
);
|
|
153
|
+
res.json({
|
|
154
|
+
items,
|
|
155
|
+
offset: response.NextMarker
|
|
156
|
+
});
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.error("Error listing media");
|
|
159
|
+
console.error(e);
|
|
160
|
+
res.status(500);
|
|
161
|
+
const message = findErrorMessage(e);
|
|
162
|
+
res.json({ e: message });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
var findErrorMessage = (e) => {
|
|
166
|
+
if (typeof e == "string")
|
|
167
|
+
return e;
|
|
168
|
+
if (e.message)
|
|
169
|
+
return e.message;
|
|
170
|
+
if (e.error && e.error.message)
|
|
171
|
+
return e.error.message;
|
|
172
|
+
return "an error occurred";
|
|
173
|
+
};
|
|
174
|
+
async function deleteAsset(req, res, client, bucket) {
|
|
175
|
+
const { media } = req.query;
|
|
176
|
+
const [, objectKey] = media;
|
|
177
|
+
const params = {
|
|
178
|
+
Bucket: bucket,
|
|
179
|
+
Key: objectKey
|
|
180
|
+
};
|
|
181
|
+
try {
|
|
182
|
+
const data = await client.send(new DeleteObjectCommand(params));
|
|
183
|
+
res.json(data);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.error("Error deleting media");
|
|
186
|
+
console.error(err);
|
|
187
|
+
res.status(500).json({
|
|
188
|
+
message: err.message || "Something went wrong"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function getDOSToTinaFunc(cdnUrl, mediaRoot) {
|
|
193
|
+
return function dosToTina(file) {
|
|
194
|
+
const strippedKey = stripMediaRoot(mediaRoot, file.Key);
|
|
195
|
+
const filename = path.basename(strippedKey);
|
|
196
|
+
const directory = path.dirname(strippedKey) + "/";
|
|
197
|
+
const src = cdnUrl + file.Key;
|
|
198
|
+
return {
|
|
199
|
+
id: file.Key,
|
|
200
|
+
filename,
|
|
201
|
+
directory,
|
|
202
|
+
src,
|
|
203
|
+
thumbnails: {
|
|
204
|
+
"75x75": src,
|
|
205
|
+
"400x400": src,
|
|
206
|
+
"1000x1000": src
|
|
207
|
+
},
|
|
208
|
+
type: "file"
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
createMediaHandler,
|
|
214
|
+
mediaHandlerConfig
|
|
215
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-tinacms-dos",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-20240903031924",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"files": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"tinacms": "0.0.0-
|
|
21
|
+
"tinacms": "0.0.0-20240903031924"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/crypto-js": "^3.1.47",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"react": "^18.3.1",
|
|
29
29
|
"react-dom": "^18.3.1",
|
|
30
30
|
"typescript": "4.6.4",
|
|
31
|
-
"@tinacms/scripts": "0.0.0-
|
|
32
|
-
"tinacms": "0.0.0-
|
|
31
|
+
"@tinacms/scripts": "0.0.0-20240903031924",
|
|
32
|
+
"tinacms": "0.0.0-20240903031924"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"registry": "https://registry.npmjs.org"
|