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" ;
@@ -30,76 +28,15 @@ export default class UploadPlugin extends AdminForthPlugin {
30
28
}
31
29
32
30
async setupLifecycleRule ( ) {
33
- // check that lifecyle rule "adminforth-unused-cleaner" exists
34
- const CLEANUP_RULE_ID = 'adminforth-unused-cleaner' ;
35
-
36
- const s3 = new S3 ( {
37
- credentials : {
38
- accessKeyId : this . options . s3AccessKeyId ,
39
- secretAccessKey : this . options . s3SecretAccessKey ,
40
- } ,
41
- region : this . options . s3Region ,
42
- } ) ;
43
-
44
- // check bucket exists
45
- const bucketExists = s3 . headBucket ( { Bucket : this . options . s3Bucket } )
46
- if ( ! bucketExists ) {
47
- throw new Error ( `Bucket ${ this . options . s3Bucket } does not exist` ) ;
48
- }
49
-
50
- // check that lifecycle rule exists
51
- let ruleExists : boolean = false ;
52
-
53
- try {
54
- const lifecycleConfig : any = await s3 . getBucketLifecycleConfiguration ( { Bucket : this . options . s3Bucket } ) ;
55
- ruleExists = lifecycleConfig . Rules . some ( ( rule : any ) => rule . ID === CLEANUP_RULE_ID ) ;
56
- } catch ( e : any ) {
57
- if ( e . name !== 'NoSuchLifecycleConfiguration' ) {
58
- console . error ( `⛔ Error checking lifecycle configuration, please check keys have permissions to
59
- getBucketLifecycleConfiguration on bucket ${ this . options . s3Bucket } in region ${ this . options . s3Region } . Exception:` , e ) ;
60
- throw e ;
61
- } else {
62
- ruleExists = false ;
63
- }
64
- }
65
-
66
- if ( ! ruleExists ) {
67
- // create
68
- // rule deletes object has tag adminforth-candidate-for-cleanup = true after 2 days
69
- const params = {
70
- Bucket : this . options . s3Bucket ,
71
- LifecycleConfiguration : {
72
- Rules : [
73
- {
74
- ID : CLEANUP_RULE_ID ,
75
- Status : ExpirationStatus . Enabled ,
76
- Filter : {
77
- Tag : {
78
- Key : ADMINFORTH_NOT_YET_USED_TAG ,
79
- Value : 'true'
80
- }
81
- } ,
82
- Expiration : {
83
- Days : 2
84
- }
85
- }
86
- ]
87
- }
88
- } ;
89
-
90
- await s3 . putBucketLifecycleConfiguration ( params ) ;
91
- }
31
+ this . options . storage . adapter . setupLifecycle ( ) ;
92
32
}
93
33
94
- async genPreviewUrl ( record : any , s3 : S3 ) {
34
+ async genPreviewUrl ( record : any ) {
95
35
if ( this . options . preview ?. previewUrl ) {
96
- record [ `previewUrl_${ this . pluginInstanceId } ` ] = this . options . preview . previewUrl ( { s3Path : record [ this . options . pathColumnName ] } ) ;
36
+ record [ `previewUrl_${ this . pluginInstanceId } ` ] = this . options . preview . previewUrl ( { filePath : record [ this . options . pathColumnName ] } ) ;
97
37
return ;
98
38
}
99
- const previewUrl = await await getSignedUrl ( s3 , new GetObjectCommand ( {
100
- Bucket : this . options . s3Bucket ,
101
- Key : record [ this . options . pathColumnName ] ,
102
- } ) ) ;
39
+ const previewUrl = await this . options . storage . adapter . getDownloadUrl ( record [ this . options . pathColumnName ] , 1800 ) ;
103
40
104
41
record [ `previewUrl_${ this . pluginInstanceId } ` ] = previewUrl ;
105
42
}
@@ -222,23 +159,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
222
159
process . env . HEAVY_DEBUG && console . log ( '💾💾 after save ' , record ?. id ) ;
223
160
224
161
if ( record [ pathColumnName ] ) {
225
- const s3 = new S3 ( {
226
- credentials : {
227
- accessKeyId : this . options . s3AccessKeyId ,
228
- secretAccessKey : this . options . s3SecretAccessKey ,
229
- } ,
230
-
231
- region : this . options . s3Region ,
232
- } ) ;
233
162
process . env . HEAVY_DEBUG && console . log ( '🪥🪥 remove ObjectTagging' , record [ pathColumnName ] ) ;
234
163
// let it crash if it fails: this is a new file which just was uploaded.
235
- await s3 . putObjectTagging ( {
236
- Bucket : this . options . s3Bucket ,
237
- Key : record [ pathColumnName ] ,
238
- Tagging : {
239
- TagSet : [ ]
240
- }
241
- } ) ;
164
+ await this . options . storage . adapter . markKeyForNotDeletation ( record [ pathColumnName ] ) ;
242
165
}
243
166
return { ok : true } ;
244
167
} ) ;
@@ -255,16 +178,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
255
178
return { ok : true } ;
256
179
}
257
180
if ( record [ pathColumnName ] ) {
258
- const s3 = new S3 ( {
259
- credentials : {
260
- accessKeyId : this . options . s3AccessKeyId ,
261
- secretAccessKey : this . options . s3SecretAccessKey ,
262
- } ,
263
-
264
- region : this . options . s3Region ,
265
- } ) ;
266
-
267
- await this . genPreviewUrl ( record , s3 ) ;
181
+ await this . genPreviewUrl ( record )
268
182
}
269
183
return { ok : true } ;
270
184
} ) ;
@@ -275,18 +189,9 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
275
189
276
190
if ( pathColumn . showIn . list ) {
277
191
resourceConfig . hooks . list . afterDatasourceResponse . push ( async ( { response } : { response : any } ) => {
278
- const s3 = new S3 ( {
279
- credentials : {
280
- accessKeyId : this . options . s3AccessKeyId ,
281
- secretAccessKey : this . options . s3SecretAccessKey ,
282
- } ,
283
-
284
- region : this . options . s3Region ,
285
- } ) ;
286
-
287
192
await Promise . all ( response . map ( async ( record : any ) => {
288
193
if ( record [ this . options . pathColumnName ] ) {
289
- await this . genPreviewUrl ( record , s3 ) ;
194
+ await this . genPreviewUrl ( record )
290
195
}
291
196
} ) ) ;
292
197
return { ok : true } ;
@@ -298,28 +203,8 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
298
203
// add delete hook which sets tag adminforth-candidate-for-cleanup to true
299
204
resourceConfig . hooks . delete . afterSave . push ( async ( { record } : { record : any } ) => {
300
205
if ( record [ pathColumnName ] ) {
301
- const s3 = new S3 ( {
302
- credentials : {
303
- accessKeyId : this . options . s3AccessKeyId ,
304
- secretAccessKey : this . options . s3SecretAccessKey ,
305
- } ,
306
-
307
- region : this . options . s3Region ,
308
- } ) ;
309
-
310
206
try {
311
- await s3 . putObjectTagging ( {
312
- Bucket : this . options . s3Bucket ,
313
- Key : record [ pathColumnName ] ,
314
- Tagging : {
315
- TagSet : [
316
- {
317
- Key : ADMINFORTH_NOT_YET_USED_TAG ,
318
- Value : 'true'
319
- }
320
- ]
321
- }
322
- } ) ;
207
+ await this . options . storage . adapter . markKeyForDeletation ( record [ pathColumnName ] ) ;
323
208
} catch ( e ) {
324
209
// file might be e.g. already deleted, so we catch error
325
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 ) ;
@@ -345,30 +230,10 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
345
230
resourceConfig . hooks . edit . afterSave . push ( async ( { updates, oldRecord } : { updates : any , oldRecord : any } ) => {
346
231
347
232
if ( updates [ virtualColumn . name ] || updates [ virtualColumn . name ] === null ) {
348
- const s3 = new S3 ( {
349
- credentials : {
350
- accessKeyId : this . options . s3AccessKeyId ,
351
- secretAccessKey : this . options . s3SecretAccessKey ,
352
- } ,
353
-
354
- region : this . options . s3Region ,
355
- } ) ;
356
-
357
233
if ( oldRecord [ pathColumnName ] ) {
358
234
// put tag to delete old file
359
235
try {
360
- await s3 . putObjectTagging ( {
361
- Bucket : this . options . s3Bucket ,
362
- Key : oldRecord [ pathColumnName ] ,
363
- Tagging : {
364
- TagSet : [
365
- {
366
- Key : ADMINFORTH_NOT_YET_USED_TAG ,
367
- Value : 'true'
368
- }
369
- ]
370
- }
371
- } ) ;
236
+ await this . options . storage . adapter . markKeyForDeletation ( oldRecord [ pathColumnName ] ) ;
372
237
} catch ( e ) {
373
238
// file might be e.g. already deleted, so we catch error
374
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 ) ;
@@ -377,13 +242,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
377
242
if ( updates [ virtualColumn . name ] !== null ) {
378
243
// remove tag from new file
379
244
// in this case we let it crash if it fails: this is a new file which just was uploaded.
380
- await s3 . putObjectTagging ( {
381
- Bucket : this . options . s3Bucket ,
382
- Key : updates [ pathColumnName ] ,
383
- Tagging : {
384
- TagSet : [ ]
385
- }
386
- } ) ;
245
+ await this . options . storage . adapter . markKeyForNotDeletation ( updates [ pathColumnName ] ) ;
387
246
}
388
247
}
389
248
return { ok : true } ;
@@ -414,7 +273,7 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
414
273
415
274
server . endpoint ( {
416
275
method : 'POST' ,
417
- path : `/plugin/${ this . pluginInstanceId } /get_s3_upload_url ` ,
276
+ path : `/plugin/${ this . pluginInstanceId } /get_file_upload_url ` ,
418
277
handler : async ( { body } ) => {
419
278
const { originalFilename, contentType, size, originalExtension, recordPk } = body ;
420
279
@@ -433,49 +292,22 @@ getBucketLifecycleConfiguration on bucket ${this.options.s3Bucket} in region ${t
433
292
)
434
293
}
435
294
436
- const s3Path : string = this . options . s3Path ( { originalFilename, originalExtension, contentType, record } ) ;
437
- if ( s3Path . startsWith ( '/' ) ) {
295
+ const filePath : string = this . options . filePath ( { originalFilename, originalExtension, contentType, record } ) ;
296
+ if ( filePath . startsWith ( '/' ) ) {
438
297
throw new Error ( 's3Path should not start with /, please adjust s3path function to not return / at the start of the path' ) ;
439
298
}
440
- const s3 = new S3 ( {
441
- credentials : {
442
- accessKeyId : this . options . s3AccessKeyId ,
443
- secretAccessKey : this . options . s3SecretAccessKey ,
444
- } ,
445
-
446
- region : this . options . s3Region ,
447
- } ) ;
448
-
449
- const tagline = `${ ADMINFORTH_NOT_YET_USED_TAG } =true` ;
450
- const params = {
451
- Bucket : this . options . s3Bucket ,
452
- Key : s3Path ,
453
- ContentType : contentType ,
454
- ACL : ( this . options . s3ACL || 'private' ) as ObjectCannedACL ,
455
- Tagging : tagline ,
456
- } ;
457
-
458
- const uploadUrl = await await getSignedUrl ( s3 , new PutObjectCommand ( params ) , {
459
- expiresIn : 1800 ,
460
- unhoistableHeaders : new Set ( [ 'x-amz-tagging' ] ) ,
461
- } ) ;
462
-
299
+ const { uploadUrl, uploadExtraParams } = await this . options . storage . adapter . getUploadSignedUrl ( filePath , contentType , 1800 ) ;
463
300
let previewUrl ;
464
301
if ( this . options . preview ?. previewUrl ) {
465
- previewUrl = this . options . preview . previewUrl ( { s3Path } ) ;
466
- } else if ( this . options . s3ACL === 'public-read' ) {
467
- previewUrl = `https://${ this . options . s3Bucket } .s3.${ this . options . s3Region } .amazonaws.com/${ s3Path } ` ;
302
+ previewUrl = this . options . preview . previewUrl ( { filePath } ) ;
468
303
} else {
469
- previewUrl = await getSignedUrl ( s3 , new GetObjectCommand ( {
470
- Bucket : this . options . s3Bucket ,
471
- Key : s3Path ,
472
- } ) ) ;
304
+ previewUrl = await this . options . storage . adapter . getDownloadUrl ( filePath , 1800 ) ;
473
305
}
474
306
475
307
return {
476
308
uploadUrl,
477
- s3Path ,
478
- tagline ,
309
+ filePath ,
310
+ uploadExtraParams ,
479
311
previewUrl,
480
312
} ;
481
313
}
0 commit comments