@adminforth/upload 1.4.6 → 1.5.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/build.log +2 -2
- package/custom/imageGenerator.vue +22 -13
- package/custom/preview.vue +0 -1
- package/custom/uploader.vue +6 -4
- package/dist/custom/imageGenerator.vue +22 -13
- package/dist/custom/preview.vue +0 -1
- package/dist/custom/uploader.vue +6 -4
- package/dist/index.js +35 -170
- package/index.ts +42 -186
- package/package.json +1 -1
- package/types.ts +11 -29
package/build.log
CHANGED
|
@@ -11,5 +11,5 @@ custom/preview.vue
|
|
|
11
11
|
custom/tsconfig.json
|
|
12
12
|
custom/uploader.vue
|
|
13
13
|
|
|
14
|
-
sent
|
|
15
|
-
total size is 42,
|
|
14
|
+
sent 43,165 bytes received 134 bytes 86,598.00 bytes/sec
|
|
15
|
+
total size is 42,676 speedup is 0.99
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
:minValue="0"
|
|
51
51
|
:maxValue="historicalAverage"
|
|
52
52
|
:showValues="false"
|
|
53
|
-
:progressFormatter="(value: number, percentage: number) => `${ formatTime(loadingTimer) } ( ${ Math.floor( (
|
|
53
|
+
:progressFormatter="(value: number, percentage: number) => `${ formatTime(loadingTimer) } ( ~ ${ Math.floor( (
|
|
54
54
|
loadingTimer < historicalAverage ? loadingTimer : historicalAverage
|
|
55
55
|
) / historicalAverage * 100) }% )`"
|
|
56
56
|
/>
|
|
@@ -252,15 +252,10 @@ async function confirmImage() {
|
|
|
252
252
|
|
|
253
253
|
const loadingTimer: Ref<number | null> = ref(null);
|
|
254
254
|
|
|
255
|
-
const historicalRuns: Ref<number[]> = ref([]);
|
|
256
255
|
|
|
257
256
|
const errorMessage: Ref<string | null> = ref(null);
|
|
258
257
|
|
|
259
|
-
const historicalAverage: Ref<number | null> =
|
|
260
|
-
if (historicalRuns.value.length === 0) return null;
|
|
261
|
-
const sum = historicalRuns.value.reduce((a, b) => a + b, 0);
|
|
262
|
-
return Math.floor(sum / historicalRuns.value.length);
|
|
263
|
-
});
|
|
258
|
+
const historicalAverage: Ref<number | null> = ref(null);
|
|
264
259
|
|
|
265
260
|
|
|
266
261
|
function formatTime(seconds: number): string {
|
|
@@ -269,6 +264,14 @@ function formatTime(seconds: number): string {
|
|
|
269
264
|
}
|
|
270
265
|
|
|
271
266
|
|
|
267
|
+
async function getHistoricalAverage() {
|
|
268
|
+
const resp = await callAdminForthApi({
|
|
269
|
+
path: `/plugin/${props.meta.pluginInstanceId}/averageDuration`,
|
|
270
|
+
method: 'GET',
|
|
271
|
+
});
|
|
272
|
+
historicalAverage.value = resp?.averageDuration || null;
|
|
273
|
+
}
|
|
274
|
+
|
|
272
275
|
async function generateImages() {
|
|
273
276
|
errorMessage.value = null;
|
|
274
277
|
loading.value = true;
|
|
@@ -279,7 +282,8 @@ async function generateImages() {
|
|
|
279
282
|
loadingTimer.value = elapsed;
|
|
280
283
|
}, 100);
|
|
281
284
|
const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
|
|
282
|
-
|
|
285
|
+
|
|
286
|
+
await getHistoricalAverage();
|
|
283
287
|
let resp = null;
|
|
284
288
|
let error = null;
|
|
285
289
|
try {
|
|
@@ -294,7 +298,6 @@ async function generateImages() {
|
|
|
294
298
|
} catch (e) {
|
|
295
299
|
console.error(e);
|
|
296
300
|
} finally {
|
|
297
|
-
historicalRuns.value.push(loadingTimer.value);
|
|
298
301
|
clearInterval(ticker);
|
|
299
302
|
loadingTimer.value = null;
|
|
300
303
|
loading.value = false;
|
|
@@ -330,12 +333,17 @@ async function generateImages() {
|
|
|
330
333
|
// ];
|
|
331
334
|
await nextTick();
|
|
332
335
|
|
|
336
|
+
|
|
333
337
|
caurosel.value = new Carousel(
|
|
334
338
|
document.getElementById('gallery'),
|
|
335
|
-
images.value.map((img, index) =>
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
+
images.value.map((img, index) => {
|
|
340
|
+
console.log('mapping image', img, index);
|
|
341
|
+
return {
|
|
342
|
+
image: img,
|
|
343
|
+
el: document.getElementById('gallery').querySelector(`[data-carousel-item]:nth-child(${index + 1})`),
|
|
344
|
+
position: index,
|
|
345
|
+
};
|
|
346
|
+
}),
|
|
339
347
|
{
|
|
340
348
|
internal: 0,
|
|
341
349
|
defaultPosition: currentIndex,
|
|
@@ -345,6 +353,7 @@ async function generateImages() {
|
|
|
345
353
|
}
|
|
346
354
|
);
|
|
347
355
|
await nextTick();
|
|
356
|
+
|
|
348
357
|
loading.value = false;
|
|
349
358
|
}
|
|
350
359
|
|
package/custom/preview.vue
CHANGED
package/custom/uploader.vue
CHANGED
|
@@ -229,8 +229,8 @@ const onFileChange = async (e) => {
|
|
|
229
229
|
reader.readAsDataURL(file);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
const { uploadUrl,
|
|
233
|
-
path: `/plugin/${props.meta.pluginInstanceId}/
|
|
232
|
+
const { uploadUrl, uploadExtraParams, filePath, error } = await callAdminForthApi({
|
|
233
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get_file_upload_url`,
|
|
234
234
|
method: 'POST',
|
|
235
235
|
body: {
|
|
236
236
|
originalFilename: nameNoExtension,
|
|
@@ -266,7 +266,9 @@ const onFileChange = async (e) => {
|
|
|
266
266
|
});
|
|
267
267
|
xhr.open('PUT', uploadUrl, true);
|
|
268
268
|
xhr.setRequestHeader('Content-Type', type);
|
|
269
|
-
|
|
269
|
+
uploadExtraParams && Object.entries(uploadExtraParams).forEach(([key, value]: [string, string]) => {
|
|
270
|
+
xhr.setRequestHeader(key, value);
|
|
271
|
+
})
|
|
270
272
|
xhr.send(file);
|
|
271
273
|
});
|
|
272
274
|
if (!success) {
|
|
@@ -284,7 +286,7 @@ const onFileChange = async (e) => {
|
|
|
284
286
|
return;
|
|
285
287
|
}
|
|
286
288
|
uploaded.value = true;
|
|
287
|
-
emit('update:value',
|
|
289
|
+
emit('update:value', filePath);
|
|
288
290
|
} catch (error) {
|
|
289
291
|
console.error('Error uploading file:', error);
|
|
290
292
|
adminforth.alert({
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
:minValue="0"
|
|
51
51
|
:maxValue="historicalAverage"
|
|
52
52
|
:showValues="false"
|
|
53
|
-
:progressFormatter="(value: number, percentage: number) => `${ formatTime(loadingTimer) } ( ${ Math.floor( (
|
|
53
|
+
:progressFormatter="(value: number, percentage: number) => `${ formatTime(loadingTimer) } ( ~ ${ Math.floor( (
|
|
54
54
|
loadingTimer < historicalAverage ? loadingTimer : historicalAverage
|
|
55
55
|
) / historicalAverage * 100) }% )`"
|
|
56
56
|
/>
|
|
@@ -252,15 +252,10 @@ async function confirmImage() {
|
|
|
252
252
|
|
|
253
253
|
const loadingTimer: Ref<number | null> = ref(null);
|
|
254
254
|
|
|
255
|
-
const historicalRuns: Ref<number[]> = ref([]);
|
|
256
255
|
|
|
257
256
|
const errorMessage: Ref<string | null> = ref(null);
|
|
258
257
|
|
|
259
|
-
const historicalAverage: Ref<number | null> =
|
|
260
|
-
if (historicalRuns.value.length === 0) return null;
|
|
261
|
-
const sum = historicalRuns.value.reduce((a, b) => a + b, 0);
|
|
262
|
-
return Math.floor(sum / historicalRuns.value.length);
|
|
263
|
-
});
|
|
258
|
+
const historicalAverage: Ref<number | null> = ref(null);
|
|
264
259
|
|
|
265
260
|
|
|
266
261
|
function formatTime(seconds: number): string {
|
|
@@ -269,6 +264,14 @@ function formatTime(seconds: number): string {
|
|
|
269
264
|
}
|
|
270
265
|
|
|
271
266
|
|
|
267
|
+
async function getHistoricalAverage() {
|
|
268
|
+
const resp = await callAdminForthApi({
|
|
269
|
+
path: `/plugin/${props.meta.pluginInstanceId}/averageDuration`,
|
|
270
|
+
method: 'GET',
|
|
271
|
+
});
|
|
272
|
+
historicalAverage.value = resp?.averageDuration || null;
|
|
273
|
+
}
|
|
274
|
+
|
|
272
275
|
async function generateImages() {
|
|
273
276
|
errorMessage.value = null;
|
|
274
277
|
loading.value = true;
|
|
@@ -279,7 +282,8 @@ async function generateImages() {
|
|
|
279
282
|
loadingTimer.value = elapsed;
|
|
280
283
|
}, 100);
|
|
281
284
|
const currentIndex = caurosel.value?.getActiveItem()?.position || 0;
|
|
282
|
-
|
|
285
|
+
|
|
286
|
+
await getHistoricalAverage();
|
|
283
287
|
let resp = null;
|
|
284
288
|
let error = null;
|
|
285
289
|
try {
|
|
@@ -294,7 +298,6 @@ async function generateImages() {
|
|
|
294
298
|
} catch (e) {
|
|
295
299
|
console.error(e);
|
|
296
300
|
} finally {
|
|
297
|
-
historicalRuns.value.push(loadingTimer.value);
|
|
298
301
|
clearInterval(ticker);
|
|
299
302
|
loadingTimer.value = null;
|
|
300
303
|
loading.value = false;
|
|
@@ -330,12 +333,17 @@ async function generateImages() {
|
|
|
330
333
|
// ];
|
|
331
334
|
await nextTick();
|
|
332
335
|
|
|
336
|
+
|
|
333
337
|
caurosel.value = new Carousel(
|
|
334
338
|
document.getElementById('gallery'),
|
|
335
|
-
images.value.map((img, index) =>
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
+
images.value.map((img, index) => {
|
|
340
|
+
console.log('mapping image', img, index);
|
|
341
|
+
return {
|
|
342
|
+
image: img,
|
|
343
|
+
el: document.getElementById('gallery').querySelector(`[data-carousel-item]:nth-child(${index + 1})`),
|
|
344
|
+
position: index,
|
|
345
|
+
};
|
|
346
|
+
}),
|
|
339
347
|
{
|
|
340
348
|
internal: 0,
|
|
341
349
|
defaultPosition: currentIndex,
|
|
@@ -345,6 +353,7 @@ async function generateImages() {
|
|
|
345
353
|
}
|
|
346
354
|
);
|
|
347
355
|
await nextTick();
|
|
356
|
+
|
|
348
357
|
loading.value = false;
|
|
349
358
|
}
|
|
350
359
|
|
package/dist/custom/preview.vue
CHANGED
package/dist/custom/uploader.vue
CHANGED
|
@@ -229,8 +229,8 @@ const onFileChange = async (e) => {
|
|
|
229
229
|
reader.readAsDataURL(file);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
const { uploadUrl,
|
|
233
|
-
path: `/plugin/${props.meta.pluginInstanceId}/
|
|
232
|
+
const { uploadUrl, uploadExtraParams, filePath, error } = await callAdminForthApi({
|
|
233
|
+
path: `/plugin/${props.meta.pluginInstanceId}/get_file_upload_url`,
|
|
234
234
|
method: 'POST',
|
|
235
235
|
body: {
|
|
236
236
|
originalFilename: nameNoExtension,
|
|
@@ -266,7 +266,9 @@ const onFileChange = async (e) => {
|
|
|
266
266
|
});
|
|
267
267
|
xhr.open('PUT', uploadUrl, true);
|
|
268
268
|
xhr.setRequestHeader('Content-Type', type);
|
|
269
|
-
|
|
269
|
+
uploadExtraParams && Object.entries(uploadExtraParams).forEach(([key, value]: [string, string]) => {
|
|
270
|
+
xhr.setRequestHeader(key, value);
|
|
271
|
+
})
|
|
270
272
|
xhr.send(file);
|
|
271
273
|
});
|
|
272
274
|
if (!success) {
|
|
@@ -284,7 +286,7 @@ const onFileChange = async (e) => {
|
|
|
284
286
|
return;
|
|
285
287
|
}
|
|
286
288
|
uploaded.value = true;
|
|
287
|
-
emit('update:value',
|
|
289
|
+
emit('update:value', filePath);
|
|
288
290
|
} catch (error) {
|
|
289
291
|
console.error('Error uploading file:', error);
|
|
290
292
|
adminforth.alert({
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
11
|
-
import { ExpirationStatus, GetObjectCommand, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
|
|
12
10
|
import { AdminForthPlugin, Filters, suggestIfTypo } from "adminforth";
|
|
13
11
|
import { Readable } from "stream";
|
|
14
12
|
import { RateLimiter } from "adminforth";
|
|
@@ -17,80 +15,26 @@ export default class UploadPlugin extends AdminForthPlugin {
|
|
|
17
15
|
constructor(options) {
|
|
18
16
|
super(options, import.meta.url);
|
|
19
17
|
this.options = options;
|
|
18
|
+
// for calcualting average time
|
|
19
|
+
this.totalCalls = 0;
|
|
20
|
+
this.totalDuration = 0;
|
|
20
21
|
}
|
|
21
22
|
instanceUniqueRepresentation(pluginOptions) {
|
|
22
23
|
return `${pluginOptions.pathColumnName}`;
|
|
23
24
|
}
|
|
24
25
|
setupLifecycleRule() {
|
|
25
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
-
|
|
27
|
-
const CLEANUP_RULE_ID = 'adminforth-unused-cleaner';
|
|
28
|
-
const s3 = new S3({
|
|
29
|
-
credentials: {
|
|
30
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
31
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
32
|
-
},
|
|
33
|
-
region: this.options.s3Region,
|
|
34
|
-
});
|
|
35
|
-
// check bucket exists
|
|
36
|
-
const bucketExists = s3.headBucket({ Bucket: this.options.s3Bucket });
|
|
37
|
-
if (!bucketExists) {
|
|
38
|
-
throw new Error(`Bucket ${this.options.s3Bucket} does not exist`);
|
|
39
|
-
}
|
|
40
|
-
// check that lifecycle rule exists
|
|
41
|
-
let ruleExists = false;
|
|
42
|
-
try {
|
|
43
|
-
const lifecycleConfig = yield s3.getBucketLifecycleConfiguration({ Bucket: this.options.s3Bucket });
|
|
44
|
-
ruleExists = lifecycleConfig.Rules.some((rule) => rule.ID === CLEANUP_RULE_ID);
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
if (e.name !== 'NoSuchLifecycleConfiguration') {
|
|
48
|
-
console.error(`⛔ Error checking lifecycle configuration, please check keys have permissions to
|
|
49
|
-
getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${this.options.s3Region}. Exception:`, e);
|
|
50
|
-
throw e;
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
ruleExists = false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (!ruleExists) {
|
|
57
|
-
// create
|
|
58
|
-
// rule deletes object has tag adminforth-candidate-for-cleanup = true after 2 days
|
|
59
|
-
const params = {
|
|
60
|
-
Bucket: this.options.s3Bucket,
|
|
61
|
-
LifecycleConfiguration: {
|
|
62
|
-
Rules: [
|
|
63
|
-
{
|
|
64
|
-
ID: CLEANUP_RULE_ID,
|
|
65
|
-
Status: ExpirationStatus.Enabled,
|
|
66
|
-
Filter: {
|
|
67
|
-
Tag: {
|
|
68
|
-
Key: ADMINFORTH_NOT_YET_USED_TAG,
|
|
69
|
-
Value: 'true'
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
Expiration: {
|
|
73
|
-
Days: 2
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
yield s3.putBucketLifecycleConfiguration(params);
|
|
80
|
-
}
|
|
27
|
+
this.options.storage.adapter.setupLifecycle();
|
|
81
28
|
});
|
|
82
29
|
}
|
|
83
|
-
genPreviewUrl(record
|
|
30
|
+
genPreviewUrl(record) {
|
|
84
31
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
32
|
var _a;
|
|
86
33
|
if ((_a = this.options.preview) === null || _a === void 0 ? void 0 : _a.previewUrl) {
|
|
87
|
-
record[`previewUrl_${this.pluginInstanceId}`] = this.options.preview.previewUrl({
|
|
34
|
+
record[`previewUrl_${this.pluginInstanceId}`] = this.options.preview.previewUrl({ filePath: record[this.options.pathColumnName] });
|
|
88
35
|
return;
|
|
89
36
|
}
|
|
90
|
-
const previewUrl = yield
|
|
91
|
-
Bucket: this.options.s3Bucket,
|
|
92
|
-
Key: record[this.options.pathColumnName],
|
|
93
|
-
}));
|
|
37
|
+
const previewUrl = yield this.options.storage.adapter.getDownloadUrl(record[this.options.pathColumnName], 1800);
|
|
94
38
|
record[`previewUrl_${this.pluginInstanceId}`] = previewUrl;
|
|
95
39
|
});
|
|
96
40
|
}
|
|
@@ -202,22 +146,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
202
146
|
resourceConfig.hooks.create.afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ record }) {
|
|
203
147
|
process.env.HEAVY_DEBUG && console.log('💾💾 after save ', record === null || record === void 0 ? void 0 : record.id);
|
|
204
148
|
if (record[pathColumnName]) {
|
|
205
|
-
const s3 = new S3({
|
|
206
|
-
credentials: {
|
|
207
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
208
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
209
|
-
},
|
|
210
|
-
region: this.options.s3Region,
|
|
211
|
-
});
|
|
212
149
|
process.env.HEAVY_DEBUG && console.log('🪥🪥 remove ObjectTagging', record[pathColumnName]);
|
|
213
150
|
// let it crash if it fails: this is a new file which just was uploaded.
|
|
214
|
-
yield
|
|
215
|
-
Bucket: this.options.s3Bucket,
|
|
216
|
-
Key: record[pathColumnName],
|
|
217
|
-
Tagging: {
|
|
218
|
-
TagSet: []
|
|
219
|
-
}
|
|
220
|
-
});
|
|
151
|
+
yield this.options.storage.adapter.markKeyForNotDeletation(record[pathColumnName]);
|
|
221
152
|
}
|
|
222
153
|
return { ok: true };
|
|
223
154
|
}));
|
|
@@ -230,14 +161,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
230
161
|
return { ok: true };
|
|
231
162
|
}
|
|
232
163
|
if (record[pathColumnName]) {
|
|
233
|
-
|
|
234
|
-
credentials: {
|
|
235
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
236
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
237
|
-
},
|
|
238
|
-
region: this.options.s3Region,
|
|
239
|
-
});
|
|
240
|
-
yield this.genPreviewUrl(record, s3);
|
|
164
|
+
yield this.genPreviewUrl(record);
|
|
241
165
|
}
|
|
242
166
|
return { ok: true };
|
|
243
167
|
}));
|
|
@@ -245,16 +169,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
245
169
|
// ** HOOKS FOR LIST **//
|
|
246
170
|
if (pathColumn.showIn.list) {
|
|
247
171
|
resourceConfig.hooks.list.afterDatasourceResponse.push((_a) => __awaiter(this, [_a], void 0, function* ({ response }) {
|
|
248
|
-
const s3 = new S3({
|
|
249
|
-
credentials: {
|
|
250
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
251
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
252
|
-
},
|
|
253
|
-
region: this.options.s3Region,
|
|
254
|
-
});
|
|
255
172
|
yield Promise.all(response.map((record) => __awaiter(this, void 0, void 0, function* () {
|
|
256
173
|
if (record[this.options.pathColumnName]) {
|
|
257
|
-
yield this.genPreviewUrl(record
|
|
174
|
+
yield this.genPreviewUrl(record);
|
|
258
175
|
}
|
|
259
176
|
})));
|
|
260
177
|
return { ok: true };
|
|
@@ -264,26 +181,8 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
264
181
|
// add delete hook which sets tag adminforth-candidate-for-cleanup to true
|
|
265
182
|
resourceConfig.hooks.delete.afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ record }) {
|
|
266
183
|
if (record[pathColumnName]) {
|
|
267
|
-
const s3 = new S3({
|
|
268
|
-
credentials: {
|
|
269
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
270
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
271
|
-
},
|
|
272
|
-
region: this.options.s3Region,
|
|
273
|
-
});
|
|
274
184
|
try {
|
|
275
|
-
yield
|
|
276
|
-
Bucket: this.options.s3Bucket,
|
|
277
|
-
Key: record[pathColumnName],
|
|
278
|
-
Tagging: {
|
|
279
|
-
TagSet: [
|
|
280
|
-
{
|
|
281
|
-
Key: ADMINFORTH_NOT_YET_USED_TAG,
|
|
282
|
-
Value: 'true'
|
|
283
|
-
}
|
|
284
|
-
]
|
|
285
|
-
}
|
|
286
|
-
});
|
|
185
|
+
yield this.options.storage.adapter.markKeyForDeletation(record[pathColumnName]);
|
|
287
186
|
}
|
|
288
187
|
catch (e) {
|
|
289
188
|
// file might be e.g. already deleted, so we catch error
|
|
@@ -304,28 +203,10 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
304
203
|
// add edit postSave hook to delete old file and remove tag from new file
|
|
305
204
|
resourceConfig.hooks.edit.afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ updates, oldRecord }) {
|
|
306
205
|
if (updates[virtualColumn.name] || updates[virtualColumn.name] === null) {
|
|
307
|
-
const s3 = new S3({
|
|
308
|
-
credentials: {
|
|
309
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
310
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
311
|
-
},
|
|
312
|
-
region: this.options.s3Region,
|
|
313
|
-
});
|
|
314
206
|
if (oldRecord[pathColumnName]) {
|
|
315
207
|
// put tag to delete old file
|
|
316
208
|
try {
|
|
317
|
-
yield
|
|
318
|
-
Bucket: this.options.s3Bucket,
|
|
319
|
-
Key: oldRecord[pathColumnName],
|
|
320
|
-
Tagging: {
|
|
321
|
-
TagSet: [
|
|
322
|
-
{
|
|
323
|
-
Key: ADMINFORTH_NOT_YET_USED_TAG,
|
|
324
|
-
Value: 'true'
|
|
325
|
-
}
|
|
326
|
-
]
|
|
327
|
-
}
|
|
328
|
-
});
|
|
209
|
+
yield this.options.storage.adapter.markKeyForDeletation(oldRecord[pathColumnName]);
|
|
329
210
|
}
|
|
330
211
|
catch (e) {
|
|
331
212
|
// file might be e.g. already deleted, so we catch error
|
|
@@ -335,13 +216,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
335
216
|
if (updates[virtualColumn.name] !== null) {
|
|
336
217
|
// remove tag from new file
|
|
337
218
|
// in this case we let it crash if it fails: this is a new file which just was uploaded.
|
|
338
|
-
yield
|
|
339
|
-
Bucket: this.options.s3Bucket,
|
|
340
|
-
Key: updates[pathColumnName],
|
|
341
|
-
Tagging: {
|
|
342
|
-
TagSet: []
|
|
343
|
-
}
|
|
344
|
-
});
|
|
219
|
+
yield this.options.storage.adapter.markKeyForNotDeletation(updates[pathColumnName]);
|
|
345
220
|
}
|
|
346
221
|
}
|
|
347
222
|
return { ok: true };
|
|
@@ -354,9 +229,20 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
354
229
|
this.setupLifecycleRule();
|
|
355
230
|
}
|
|
356
231
|
setupEndpoints(server) {
|
|
232
|
+
server.endpoint({
|
|
233
|
+
method: 'GET',
|
|
234
|
+
path: `/plugin/${this.pluginInstanceId}/averageDuration`,
|
|
235
|
+
handler: () => __awaiter(this, void 0, void 0, function* () {
|
|
236
|
+
return {
|
|
237
|
+
totalCalls: this.totalCalls,
|
|
238
|
+
totalDuration: this.totalDuration,
|
|
239
|
+
averageDuration: this.totalCalls ? this.totalDuration / this.totalCalls : null,
|
|
240
|
+
};
|
|
241
|
+
})
|
|
242
|
+
});
|
|
357
243
|
server.endpoint({
|
|
358
244
|
method: 'POST',
|
|
359
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
245
|
+
path: `/plugin/${this.pluginInstanceId}/get_file_upload_url`,
|
|
360
246
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
|
|
361
247
|
var _b, _c;
|
|
362
248
|
const { originalFilename, contentType, size, originalExtension, recordPk } = body;
|
|
@@ -371,46 +257,22 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
371
257
|
const pkName = (_b = this.resourceConfig.columns.find((column) => column.primaryKey)) === null || _b === void 0 ? void 0 : _b.name;
|
|
372
258
|
record = yield this.adminforth.resource(this.resourceConfig.resourceId).get([Filters.EQ(pkName, recordPk)]);
|
|
373
259
|
}
|
|
374
|
-
const
|
|
375
|
-
if (
|
|
260
|
+
const filePath = this.options.filePath({ originalFilename, originalExtension, contentType, record });
|
|
261
|
+
if (filePath.startsWith('/')) {
|
|
376
262
|
throw new Error('s3Path should not start with /, please adjust s3path function to not return / at the start of the path');
|
|
377
263
|
}
|
|
378
|
-
const
|
|
379
|
-
credentials: {
|
|
380
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
381
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
382
|
-
},
|
|
383
|
-
region: this.options.s3Region,
|
|
384
|
-
});
|
|
385
|
-
const tagline = `${ADMINFORTH_NOT_YET_USED_TAG}=true`;
|
|
386
|
-
const params = {
|
|
387
|
-
Bucket: this.options.s3Bucket,
|
|
388
|
-
Key: s3Path,
|
|
389
|
-
ContentType: contentType,
|
|
390
|
-
ACL: (this.options.s3ACL || 'private'),
|
|
391
|
-
Tagging: tagline,
|
|
392
|
-
};
|
|
393
|
-
const uploadUrl = yield yield getSignedUrl(s3, new PutObjectCommand(params), {
|
|
394
|
-
expiresIn: 1800,
|
|
395
|
-
unhoistableHeaders: new Set(['x-amz-tagging']),
|
|
396
|
-
});
|
|
264
|
+
const { uploadUrl, uploadExtraParams } = yield this.options.storage.adapter.getUploadSignedUrl(filePath, contentType, 1800);
|
|
397
265
|
let previewUrl;
|
|
398
266
|
if ((_c = this.options.preview) === null || _c === void 0 ? void 0 : _c.previewUrl) {
|
|
399
|
-
previewUrl = this.options.preview.previewUrl({
|
|
400
|
-
}
|
|
401
|
-
else if (this.options.s3ACL === 'public-read') {
|
|
402
|
-
previewUrl = `https://${this.options.s3Bucket}.s3.${this.options.s3Region}.amazonaws.com/${s3Path}`;
|
|
267
|
+
previewUrl = this.options.preview.previewUrl({ filePath });
|
|
403
268
|
}
|
|
404
269
|
else {
|
|
405
|
-
previewUrl = yield
|
|
406
|
-
Bucket: this.options.s3Bucket,
|
|
407
|
-
Key: s3Path,
|
|
408
|
-
}));
|
|
270
|
+
previewUrl = yield this.options.storage.adapter.getDownloadUrl(filePath, 1800);
|
|
409
271
|
}
|
|
410
272
|
return {
|
|
411
273
|
uploadUrl,
|
|
412
|
-
|
|
413
|
-
|
|
274
|
+
filePath,
|
|
275
|
+
uploadExtraParams,
|
|
414
276
|
previewUrl,
|
|
415
277
|
};
|
|
416
278
|
})
|
|
@@ -467,6 +329,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
467
329
|
yield new Promise((resolve) => setTimeout(resolve, 2000));
|
|
468
330
|
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
469
331
|
}
|
|
332
|
+
const start = +new Date();
|
|
470
333
|
const resp = yield this.options.generation.adapter.generate({
|
|
471
334
|
prompt,
|
|
472
335
|
inputFiles: attachmentFiles,
|
|
@@ -478,6 +341,8 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
478
341
|
error = resp.error;
|
|
479
342
|
return;
|
|
480
343
|
}
|
|
344
|
+
this.totalCalls++;
|
|
345
|
+
this.totalDuration += (+new Date() - start) / 1000;
|
|
481
346
|
return resp.imageURLs[0];
|
|
482
347
|
})));
|
|
483
348
|
return { error, images };
|
package/index.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
import { PluginOptions } from './types.js';
|
|
3
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
4
|
-
import { ExpirationStatus, GetObjectCommand, ObjectCannedACL, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
|
|
5
3
|
import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResource, Filters, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
|
|
6
4
|
import { Readable } from "stream";
|
|
7
5
|
import { RateLimiter } from "adminforth";
|
|
@@ -13,9 +11,16 @@ export default class UploadPlugin extends AdminForthPlugin {
|
|
|
13
11
|
|
|
14
12
|
adminforth!: IAdminForth;
|
|
15
13
|
|
|
14
|
+
totalCalls: number;
|
|
15
|
+
totalDuration: number;
|
|
16
|
+
|
|
16
17
|
constructor(options: PluginOptions) {
|
|
17
18
|
super(options, import.meta.url);
|
|
18
19
|
this.options = options;
|
|
20
|
+
|
|
21
|
+
// for calcualting average time
|
|
22
|
+
this.totalCalls = 0;
|
|
23
|
+
this.totalDuration = 0;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
instanceUniqueRepresentation(pluginOptions: any) : string {
|
|
@@ -23,76 +28,15 @@ export default class UploadPlugin extends AdminForthPlugin {
|
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
async setupLifecycleRule() {
|
|
26
|
-
|
|
27
|
-
const CLEANUP_RULE_ID = 'adminforth-unused-cleaner';
|
|
28
|
-
|
|
29
|
-
const s3 = new S3({
|
|
30
|
-
credentials: {
|
|
31
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
32
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
33
|
-
},
|
|
34
|
-
region: this.options.s3Region,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// check bucket exists
|
|
38
|
-
const bucketExists = s3.headBucket({ Bucket: this.options.s3Bucket })
|
|
39
|
-
if (!bucketExists) {
|
|
40
|
-
throw new Error(`Bucket ${this.options.s3Bucket} does not exist`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// check that lifecycle rule exists
|
|
44
|
-
let ruleExists: boolean = false;
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const lifecycleConfig: any = await s3.getBucketLifecycleConfiguration({ Bucket: this.options.s3Bucket });
|
|
48
|
-
ruleExists = lifecycleConfig.Rules.some((rule: any) => rule.ID === CLEANUP_RULE_ID);
|
|
49
|
-
} catch (e: any) {
|
|
50
|
-
if (e.name !== 'NoSuchLifecycleConfiguration') {
|
|
51
|
-
console.error(`⛔ Error checking lifecycle configuration, please check keys have permissions to
|
|
52
|
-
getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${this.options.s3Region}. Exception:`, e);
|
|
53
|
-
throw e;
|
|
54
|
-
} else {
|
|
55
|
-
ruleExists = false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!ruleExists) {
|
|
60
|
-
// create
|
|
61
|
-
// rule deletes object has tag adminforth-candidate-for-cleanup = true after 2 days
|
|
62
|
-
const params = {
|
|
63
|
-
Bucket: this.options.s3Bucket,
|
|
64
|
-
LifecycleConfiguration: {
|
|
65
|
-
Rules: [
|
|
66
|
-
{
|
|
67
|
-
ID: CLEANUP_RULE_ID,
|
|
68
|
-
Status: ExpirationStatus.Enabled,
|
|
69
|
-
Filter: {
|
|
70
|
-
Tag: {
|
|
71
|
-
Key: ADMINFORTH_NOT_YET_USED_TAG,
|
|
72
|
-
Value: 'true'
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
Expiration: {
|
|
76
|
-
Days: 2
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
await s3.putBucketLifecycleConfiguration(params);
|
|
84
|
-
}
|
|
31
|
+
this.options.storage.adapter.setupLifecycle();
|
|
85
32
|
}
|
|
86
33
|
|
|
87
|
-
async genPreviewUrl(record: any
|
|
34
|
+
async genPreviewUrl(record: any) {
|
|
88
35
|
if (this.options.preview?.previewUrl) {
|
|
89
|
-
record[`previewUrl_${this.pluginInstanceId}`] = this.options.preview.previewUrl({
|
|
36
|
+
record[`previewUrl_${this.pluginInstanceId}`] = this.options.preview.previewUrl({ filePath: record[this.options.pathColumnName] });
|
|
90
37
|
return;
|
|
91
38
|
}
|
|
92
|
-
const previewUrl = await
|
|
93
|
-
Bucket: this.options.s3Bucket,
|
|
94
|
-
Key: record[this.options.pathColumnName],
|
|
95
|
-
}));
|
|
39
|
+
const previewUrl = await this.options.storage.adapter.getDownloadUrl(record[this.options.pathColumnName], 1800);
|
|
96
40
|
|
|
97
41
|
record[`previewUrl_${this.pluginInstanceId}`] = previewUrl;
|
|
98
42
|
}
|
|
@@ -215,23 +159,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
215
159
|
process.env.HEAVY_DEBUG && console.log('💾💾 after save ', record?.id);
|
|
216
160
|
|
|
217
161
|
if (record[pathColumnName]) {
|
|
218
|
-
const s3 = new S3({
|
|
219
|
-
credentials: {
|
|
220
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
221
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
region: this.options.s3Region,
|
|
225
|
-
});
|
|
226
162
|
process.env.HEAVY_DEBUG && console.log('🪥🪥 remove ObjectTagging', record[pathColumnName]);
|
|
227
163
|
// let it crash if it fails: this is a new file which just was uploaded.
|
|
228
|
-
await
|
|
229
|
-
Bucket: this.options.s3Bucket,
|
|
230
|
-
Key: record[pathColumnName],
|
|
231
|
-
Tagging: {
|
|
232
|
-
TagSet: []
|
|
233
|
-
}
|
|
234
|
-
});
|
|
164
|
+
await this.options.storage.adapter.markKeyForNotDeletation(record[pathColumnName]);
|
|
235
165
|
}
|
|
236
166
|
return { ok: true };
|
|
237
167
|
});
|
|
@@ -248,16 +178,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
248
178
|
return { ok: true };
|
|
249
179
|
}
|
|
250
180
|
if (record[pathColumnName]) {
|
|
251
|
-
|
|
252
|
-
credentials: {
|
|
253
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
254
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
region: this.options.s3Region,
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
await this.genPreviewUrl(record, s3);
|
|
181
|
+
await this.genPreviewUrl(record)
|
|
261
182
|
}
|
|
262
183
|
return { ok: true };
|
|
263
184
|
});
|
|
@@ -268,18 +189,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
268
189
|
|
|
269
190
|
if (pathColumn.showIn.list) {
|
|
270
191
|
resourceConfig.hooks.list.afterDatasourceResponse.push(async ({ response }: { response: any }) => {
|
|
271
|
-
const s3 = new S3({
|
|
272
|
-
credentials: {
|
|
273
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
274
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
region: this.options.s3Region,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
192
|
await Promise.all(response.map(async (record: any) => {
|
|
281
193
|
if (record[this.options.pathColumnName]) {
|
|
282
|
-
await this.genPreviewUrl(record
|
|
194
|
+
await this.genPreviewUrl(record)
|
|
283
195
|
}
|
|
284
196
|
}));
|
|
285
197
|
return { ok: true };
|
|
@@ -291,28 +203,8 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
291
203
|
// add delete hook which sets tag adminforth-candidate-for-cleanup to true
|
|
292
204
|
resourceConfig.hooks.delete.afterSave.push(async ({ record }: { record: any }) => {
|
|
293
205
|
if (record[pathColumnName]) {
|
|
294
|
-
const s3 = new S3({
|
|
295
|
-
credentials: {
|
|
296
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
297
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
region: this.options.s3Region,
|
|
301
|
-
});
|
|
302
|
-
|
|
303
206
|
try {
|
|
304
|
-
await
|
|
305
|
-
Bucket: this.options.s3Bucket,
|
|
306
|
-
Key: record[pathColumnName],
|
|
307
|
-
Tagging: {
|
|
308
|
-
TagSet: [
|
|
309
|
-
{
|
|
310
|
-
Key: ADMINFORTH_NOT_YET_USED_TAG,
|
|
311
|
-
Value: 'true'
|
|
312
|
-
}
|
|
313
|
-
]
|
|
314
|
-
}
|
|
315
|
-
});
|
|
207
|
+
await this.options.storage.adapter.markKeyForDeletation(record[pathColumnName]);
|
|
316
208
|
} catch (e) {
|
|
317
209
|
// file might be e.g. already deleted, so we catch error
|
|
318
210
|
console.error(`Error setting tag ${ADMINFORTH_NOT_YET_USED_TAG} to true for object ${record[pathColumnName]}. File will not be auto-cleaned up`, e);
|
|
@@ -338,30 +230,10 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
338
230
|
resourceConfig.hooks.edit.afterSave.push(async ({ updates, oldRecord }: { updates: any, oldRecord: any }) => {
|
|
339
231
|
|
|
340
232
|
if (updates[virtualColumn.name] || updates[virtualColumn.name] === null) {
|
|
341
|
-
const s3 = new S3({
|
|
342
|
-
credentials: {
|
|
343
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
344
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
region: this.options.s3Region,
|
|
348
|
-
});
|
|
349
|
-
|
|
350
233
|
if (oldRecord[pathColumnName]) {
|
|
351
234
|
// put tag to delete old file
|
|
352
235
|
try {
|
|
353
|
-
await
|
|
354
|
-
Bucket: this.options.s3Bucket,
|
|
355
|
-
Key: oldRecord[pathColumnName],
|
|
356
|
-
Tagging: {
|
|
357
|
-
TagSet: [
|
|
358
|
-
{
|
|
359
|
-
Key: ADMINFORTH_NOT_YET_USED_TAG,
|
|
360
|
-
Value: 'true'
|
|
361
|
-
}
|
|
362
|
-
]
|
|
363
|
-
}
|
|
364
|
-
});
|
|
236
|
+
await this.options.storage.adapter.markKeyForDeletation(oldRecord[pathColumnName]);
|
|
365
237
|
} catch (e) {
|
|
366
238
|
// file might be e.g. already deleted, so we catch error
|
|
367
239
|
console.error(`Error setting tag ${ADMINFORTH_NOT_YET_USED_TAG} to true for object ${oldRecord[pathColumnName]}. File will not be auto-cleaned up`, e);
|
|
@@ -370,13 +242,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
370
242
|
if (updates[virtualColumn.name] !== null) {
|
|
371
243
|
// remove tag from new file
|
|
372
244
|
// in this case we let it crash if it fails: this is a new file which just was uploaded.
|
|
373
|
-
await
|
|
374
|
-
Bucket: this.options.s3Bucket,
|
|
375
|
-
Key: updates[pathColumnName],
|
|
376
|
-
Tagging: {
|
|
377
|
-
TagSet: []
|
|
378
|
-
}
|
|
379
|
-
});
|
|
245
|
+
await this.options.storage.adapter.markKeyForNotDeletation(updates[pathColumnName]);
|
|
380
246
|
}
|
|
381
247
|
}
|
|
382
248
|
return { ok: true };
|
|
@@ -390,11 +256,24 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
390
256
|
// called here because modifyResourceConfig can be called in build time where there is no environment and AWS secrets
|
|
391
257
|
this.setupLifecycleRule();
|
|
392
258
|
}
|
|
259
|
+
|
|
393
260
|
|
|
394
261
|
setupEndpoints(server: IHttpServer) {
|
|
262
|
+
server.endpoint({
|
|
263
|
+
method: 'GET',
|
|
264
|
+
path: `/plugin/${this.pluginInstanceId}/averageDuration`,
|
|
265
|
+
handler: async () => {
|
|
266
|
+
return {
|
|
267
|
+
totalCalls: this.totalCalls,
|
|
268
|
+
totalDuration: this.totalDuration,
|
|
269
|
+
averageDuration: this.totalCalls ? this.totalDuration / this.totalCalls : null,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
395
274
|
server.endpoint({
|
|
396
275
|
method: 'POST',
|
|
397
|
-
path: `/plugin/${this.pluginInstanceId}/
|
|
276
|
+
path: `/plugin/${this.pluginInstanceId}/get_file_upload_url`,
|
|
398
277
|
handler: async ({ body }) => {
|
|
399
278
|
const { originalFilename, contentType, size, originalExtension, recordPk } = body;
|
|
400
279
|
|
|
@@ -413,49 +292,22 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
413
292
|
)
|
|
414
293
|
}
|
|
415
294
|
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
295
|
+
const filePath: string = this.options.filePath({ originalFilename, originalExtension, contentType, record });
|
|
296
|
+
if (filePath.startsWith('/')) {
|
|
418
297
|
throw new Error('s3Path should not start with /, please adjust s3path function to not return / at the start of the path');
|
|
419
298
|
}
|
|
420
|
-
const
|
|
421
|
-
credentials: {
|
|
422
|
-
accessKeyId: this.options.s3AccessKeyId,
|
|
423
|
-
secretAccessKey: this.options.s3SecretAccessKey,
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
region: this.options.s3Region,
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
const tagline = `${ADMINFORTH_NOT_YET_USED_TAG}=true`;
|
|
430
|
-
const params = {
|
|
431
|
-
Bucket: this.options.s3Bucket,
|
|
432
|
-
Key: s3Path,
|
|
433
|
-
ContentType: contentType,
|
|
434
|
-
ACL: (this.options.s3ACL || 'private') as ObjectCannedACL,
|
|
435
|
-
Tagging: tagline,
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
const uploadUrl = await await getSignedUrl(s3, new PutObjectCommand(params), {
|
|
439
|
-
expiresIn: 1800,
|
|
440
|
-
unhoistableHeaders: new Set(['x-amz-tagging']),
|
|
441
|
-
});
|
|
442
|
-
|
|
299
|
+
const { uploadUrl, uploadExtraParams } = await this.options.storage.adapter.getUploadSignedUrl(filePath, contentType, 1800);
|
|
443
300
|
let previewUrl;
|
|
444
301
|
if (this.options.preview?.previewUrl) {
|
|
445
|
-
previewUrl = this.options.preview.previewUrl({
|
|
446
|
-
} else if (this.options.s3ACL === 'public-read') {
|
|
447
|
-
previewUrl = `https://${this.options.s3Bucket}.s3.${this.options.s3Region}.amazonaws.com/${s3Path}`;
|
|
302
|
+
previewUrl = this.options.preview.previewUrl({ filePath });
|
|
448
303
|
} else {
|
|
449
|
-
previewUrl = await
|
|
450
|
-
Bucket: this.options.s3Bucket,
|
|
451
|
-
Key: s3Path,
|
|
452
|
-
}));
|
|
304
|
+
previewUrl = await this.options.storage.adapter.getDownloadUrl(filePath, 1800);
|
|
453
305
|
}
|
|
454
306
|
|
|
455
307
|
return {
|
|
456
308
|
uploadUrl,
|
|
457
|
-
|
|
458
|
-
|
|
309
|
+
filePath,
|
|
310
|
+
uploadExtraParams,
|
|
459
311
|
previewUrl,
|
|
460
312
|
};
|
|
461
313
|
}
|
|
@@ -527,6 +379,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
527
379
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
528
380
|
return `https://picsum.photos/200/300?random=${Math.floor(Math.random() * 1000)}`;
|
|
529
381
|
}
|
|
382
|
+
const start = +new Date();
|
|
530
383
|
const resp = await this.options.generation.adapter.generate(
|
|
531
384
|
{
|
|
532
385
|
prompt,
|
|
@@ -541,6 +394,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
|
|
|
541
394
|
error = resp.error;
|
|
542
395
|
return;
|
|
543
396
|
}
|
|
397
|
+
|
|
398
|
+
this.totalCalls++;
|
|
399
|
+
this.totalDuration += (+new Date() - start) / 1000;
|
|
544
400
|
|
|
545
401
|
return resp.imageURLs[0]
|
|
546
402
|
|
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdminUser, ImageGenerationAdapter } from "adminforth";
|
|
1
|
+
import { AdminUser, ImageGenerationAdapter, StorageAdapter } from "adminforth";
|
|
2
2
|
|
|
3
3
|
export type PluginOptions = {
|
|
4
4
|
|
|
@@ -18,32 +18,6 @@ export type PluginOptions = {
|
|
|
18
18
|
*/
|
|
19
19
|
maxFileSize?: number;
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* S3 bucket name where we will upload the files, e.g. 'my-bucket'
|
|
23
|
-
*/
|
|
24
|
-
s3Bucket: string,
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* S3 region, e.g. 'us-east-1'
|
|
28
|
-
*/
|
|
29
|
-
s3Region: string,
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* S3 access key id
|
|
33
|
-
*/
|
|
34
|
-
s3AccessKeyId: string,
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* S3 secret access key
|
|
38
|
-
*/
|
|
39
|
-
s3SecretAccessKey: string,
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* ACL which will be set to uploaded file, e.g. 'public-read'.
|
|
43
|
-
* If you want to use 'public-read', it is your responsibility to set the "ACL Enabled" to true in the S3 bucket policy and Uncheck "Block all public access" in the bucket settings.
|
|
44
|
-
*/
|
|
45
|
-
s3ACL?: string,
|
|
46
|
-
|
|
47
21
|
/**
|
|
48
22
|
* The path where the file will be uploaded to the S3 bucket, same path will be stored in the database
|
|
49
23
|
* in the column specified in {@link pathColumnName}
|
|
@@ -55,7 +29,7 @@ export type PluginOptions = {
|
|
|
55
29
|
* ```
|
|
56
30
|
*
|
|
57
31
|
*/
|
|
58
|
-
|
|
32
|
+
filePath: ({originalFilename, originalExtension, contentType, record }: {
|
|
59
33
|
originalFilename: string,
|
|
60
34
|
originalExtension: string,
|
|
61
35
|
contentType: string,
|
|
@@ -113,7 +87,7 @@ export type PluginOptions = {
|
|
|
113
87
|
* ```
|
|
114
88
|
*
|
|
115
89
|
*/
|
|
116
|
-
previewUrl?: ({
|
|
90
|
+
previewUrl?: ({filePath}) => string,
|
|
117
91
|
}
|
|
118
92
|
|
|
119
93
|
|
|
@@ -181,4 +155,12 @@ export type PluginOptions = {
|
|
|
181
155
|
|
|
182
156
|
}
|
|
183
157
|
|
|
158
|
+
storage?: {
|
|
159
|
+
/**
|
|
160
|
+
* The adapter used to store the files.
|
|
161
|
+
* For now only S3 adapter is supported.
|
|
162
|
+
*/
|
|
163
|
+
adapter: StorageAdapter,
|
|
164
|
+
}
|
|
165
|
+
|
|
184
166
|
}
|