@webiny/api-file-manager-s3 6.4.5 → 6.6.0-alpha.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/FileManagerS3Feature.d.ts +9 -0
- package/FileManagerS3Feature.js +34 -0
- package/FileManagerS3Feature.js.map +1 -0
- package/assetDelivery/assetDeliveryConfig.d.ts +1 -1
- package/assetDelivery/assetDeliveryConfig.js +0 -2
- package/assetDelivery/assetDeliveryConfig.js.map +1 -1
- package/assetDelivery/s3/SharpTransform.js +13 -6
- package/assetDelivery/s3/SharpTransform.js.map +1 -1
- package/assetDelivery/threatDetection/createThreatDetectionEventHandler.d.ts +21 -1
- package/assetDelivery/threatDetection/createThreatDetectionEventHandler.js +43 -27
- package/assetDelivery/threatDetection/createThreatDetectionEventHandler.js.map +1 -1
- package/assetDelivery/threatDetection/processThreatScanResult.js +9 -7
- package/assetDelivery/threatDetection/processThreatScanResult.js.map +1 -1
- package/features/DeleteFileFromBucket/DeleteS3FolderTask.d.ts +1 -1
- package/features/ExtractMetadata/ExtractMetadataTask.d.ts +1 -1
- package/features/FlushCache/FlushCacheOnFileDeleteHandler.js +1 -1
- package/features/FlushCache/FlushCacheOnFileDeleteHandler.js.map +1 -1
- package/features/FlushCache/FlushCacheOnFileUpdateHandler.js +1 -1
- package/features/FlushCache/FlushCacheOnFileUpdateHandler.js.map +1 -1
- package/features/FlushCache/InvalidateCacheTask.d.ts +2 -2
- package/features/FlushCache/InvalidateCacheTask.js +2 -2
- package/features/FlushCache/InvalidateCacheTask.js.map +1 -1
- package/features/WriteFileMetadata/MetadataWriter.js +3 -1
- package/features/WriteFileMetadata/MetadataWriter.js.map +1 -1
- package/graphql/S3GraphQLSchema.d.ts +9 -0
- package/graphql/S3GraphQLSchema.js +229 -0
- package/graphql/S3GraphQLSchema.js.map +1 -0
- package/index.d.ts +2 -2
- package/index.js +1 -29
- package/package.json +20 -18
- package/utils/FileUploadModifier.d.ts +8 -2
- package/utils/FileUploadModifier.js +5 -3
- package/utils/FileUploadModifier.js.map +1 -1
- package/utils/createFileNormalizerFromContext.js +2 -2
- package/utils/createFileNormalizerFromContext.js.map +1 -1
- package/utils/uploadFileToS3.d.ts +1 -1
- package/graphql/schema.d.ts +0 -1
- package/graphql/schema.js +0 -201
- package/graphql/schema.js.map +0 -1
- package/index.js.map +0 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Container } from "@webiny/feature/api";
|
|
2
|
+
import type { AssetDeliveryParams } from "./assetDelivery/types.js";
|
|
3
|
+
export interface FileManagerS3FeatureConfig {
|
|
4
|
+
assetDelivery?: AssetDeliveryParams;
|
|
5
|
+
}
|
|
6
|
+
export declare const FileManagerS3Feature: {
|
|
7
|
+
name: string;
|
|
8
|
+
register(container: Container, context?: FileManagerS3FeatureConfig | undefined): void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createFeature } from "@webiny/feature/api";
|
|
2
|
+
import { RequestContextInitializer } from "@webiny/event-handler-core";
|
|
3
|
+
import { WcpContext } from "@webiny/api-core/features/wcp/WcpContext/index.js";
|
|
4
|
+
import { S3GraphQLSchema } from "./graphql/S3GraphQLSchema.js";
|
|
5
|
+
import { DeleteFileFromBucketFeature } from "./features/DeleteFileFromBucket/feature.js";
|
|
6
|
+
import { WriteFileMetadataFeature } from "./features/WriteFileMetadata/feature.js";
|
|
7
|
+
import { ApplyThreatScanningFeature } from "./enterprise/ApplyThreatScanning/feature.js";
|
|
8
|
+
import { FlushCacheFeature } from "./features/FlushCache/feature.js";
|
|
9
|
+
import { ExtractMetadataFeature } from "./features/ExtractMetadata/feature.js";
|
|
10
|
+
import { GetFileContentsByIdFeature } from "./features/GetFileContentsById/feature.js";
|
|
11
|
+
import { GetFileContentsByKeyFeature } from "./features/GetFileContentsByKey/feature.js";
|
|
12
|
+
import { createS3AssetDeliveryFeature } from "./assetDelivery/feature.js";
|
|
13
|
+
const FileManagerS3Feature = createFeature({
|
|
14
|
+
name: "FileManagerS3",
|
|
15
|
+
register (container, config = {}) {
|
|
16
|
+
createS3AssetDeliveryFeature(config.assetDelivery).register(container);
|
|
17
|
+
FlushCacheFeature.register(container);
|
|
18
|
+
DeleteFileFromBucketFeature.register(container);
|
|
19
|
+
ExtractMetadataFeature.register(container);
|
|
20
|
+
WriteFileMetadataFeature.register(container);
|
|
21
|
+
GetFileContentsByIdFeature.register(container);
|
|
22
|
+
GetFileContentsByKeyFeature.register(container);
|
|
23
|
+
container.register(S3GraphQLSchema);
|
|
24
|
+
container.registerInstance(RequestContextInitializer, {
|
|
25
|
+
async init (ctx) {
|
|
26
|
+
const wcp = ctx.container.resolve(WcpContext);
|
|
27
|
+
if (wcp.canUseFileManagerThreatDetection()) ApplyThreatScanningFeature.register(ctx.container);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
export { FileManagerS3Feature };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=FileManagerS3Feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileManagerS3Feature.js","sources":["../src/FileManagerS3Feature.ts"],"sourcesContent":["import { type Container, createFeature } from \"@webiny/feature/api\";\nimport { RequestContextInitializer } from \"@webiny/event-handler-core\";\nimport { WcpContext } from \"@webiny/api-core/features/wcp/WcpContext/index.js\";\nimport { S3GraphQLSchema } from \"./graphql/S3GraphQLSchema.js\";\nimport { DeleteFileFromBucketFeature } from \"~/features/DeleteFileFromBucket/feature.js\";\nimport { WriteFileMetadataFeature } from \"~/features/WriteFileMetadata/feature.js\";\nimport { ApplyThreatScanningFeature } from \"~/enterprise/ApplyThreatScanning/feature.js\";\nimport { FlushCacheFeature } from \"~/features/FlushCache/feature.js\";\nimport { ExtractMetadataFeature } from \"~/features/ExtractMetadata/feature.js\";\nimport { GetFileContentsByIdFeature } from \"~/features/GetFileContentsById/feature.js\";\nimport { GetFileContentsByKeyFeature } from \"~/features/GetFileContentsByKey/feature.js\";\nimport { createS3AssetDeliveryFeature } from \"./assetDelivery/feature.js\";\nimport type { AssetDeliveryParams } from \"./assetDelivery/types.js\";\n\nexport interface FileManagerS3FeatureConfig {\n assetDelivery?: AssetDeliveryParams;\n}\n\nexport const FileManagerS3Feature = createFeature({\n name: \"FileManagerS3\",\n register(container: Container, config: FileManagerS3FeatureConfig = {}) {\n // Register S3-specific asset delivery implementations (S3AssetResolver, S3OutputStrategy).\n // These replace the null implementations from AssetDeliveryFeature in FileManagerAppFeature.\n createS3AssetDeliveryFeature(config.assetDelivery).register(container);\n\n // S3 file-operation features.\n FlushCacheFeature.register(container);\n DeleteFileFromBucketFeature.register(container);\n ExtractMetadataFeature.register(container);\n WriteFileMetadataFeature.register(container);\n GetFileContentsByIdFeature.register(container);\n GetFileContentsByKeyFeature.register(container);\n\n // Static S3 GraphQL schema (extends FmQuery/FmMutation) — a DI-native\n // CoreGraphQLSchemaFactory contributor (declares its resolver dependencies).\n container.register(S3GraphQLSchema);\n\n // Threat scanning is WCP-gated. The gate MUST run after the per-request WCP license refresh\n // (a RequestInitializer, pre-auth) — at register() time WcpContext still sees the NullLicense\n // and the feature would silently never register. So gate + register it in a\n // RequestContextInitializer (post-auth, post-license).\n container.registerInstance(RequestContextInitializer, {\n async init(ctx: Record<string, any>) {\n const wcp = ctx.container.resolve(WcpContext);\n if (wcp.canUseFileManagerThreatDetection()) {\n ApplyThreatScanningFeature.register(ctx.container);\n }\n }\n });\n }\n});\n"],"names":["FileManagerS3Feature","createFeature","container","config","createS3AssetDeliveryFeature","FlushCacheFeature","DeleteFileFromBucketFeature","ExtractMetadataFeature","WriteFileMetadataFeature","GetFileContentsByIdFeature","GetFileContentsByKeyFeature","S3GraphQLSchema","RequestContextInitializer","ctx","wcp","WcpContext","ApplyThreatScanningFeature"],"mappings":";;;;;;;;;;;;AAkBO,MAAMA,uBAAuBC,cAAc;IAC9C,MAAM;IACN,UAASC,SAAoB,EAAEC,SAAqC,CAAC,CAAC;QAGlEC,6BAA6BD,OAAO,aAAa,EAAE,QAAQ,CAACD;QAG5DG,kBAAkB,QAAQ,CAACH;QAC3BI,4BAA4B,QAAQ,CAACJ;QACrCK,uBAAuB,QAAQ,CAACL;QAChCM,yBAAyB,QAAQ,CAACN;QAClCO,2BAA2B,QAAQ,CAACP;QACpCQ,4BAA4B,QAAQ,CAACR;QAIrCA,UAAU,QAAQ,CAACS;QAMnBT,UAAU,gBAAgB,CAACU,2BAA2B;YAClD,MAAM,MAAKC,GAAwB;gBAC/B,MAAMC,MAAMD,IAAI,SAAS,CAAC,OAAO,CAACE;gBAClC,IAAID,IAAI,gCAAgC,IACpCE,2BAA2B,QAAQ,CAACH,IAAI,SAAS;YAEzD;QACJ;IACJ;AACJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { AssetDeliveryParams } from "../assetDelivery/types.js";
|
|
2
|
-
export declare const assetDeliveryConfig: (params: AssetDeliveryParams) =>
|
|
2
|
+
export declare const assetDeliveryConfig: (params: AssetDeliveryParams) => import("@webiny/handler").RegisterExtensionPlugin<import("@webiny/handler/types").Context>[];
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { createAssetDelivery } from "@webiny/api-file-manager";
|
|
2
1
|
import { createRegisterExtensionPlugin } from "@webiny/handler";
|
|
3
2
|
import { createS3AssetDeliveryFeature } from "./feature.js";
|
|
4
3
|
const assetDeliveryConfig = (params)=>{
|
|
5
4
|
const feature = createS3AssetDeliveryFeature(params);
|
|
6
5
|
return [
|
|
7
|
-
createAssetDelivery(),
|
|
8
6
|
createRegisterExtensionPlugin((context)=>{
|
|
9
7
|
feature.register(context.container);
|
|
10
8
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetDelivery/assetDeliveryConfig.js","sources":["../../src/assetDelivery/assetDeliveryConfig.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"assetDelivery/assetDeliveryConfig.js","sources":["../../src/assetDelivery/assetDeliveryConfig.ts"],"sourcesContent":["import { createRegisterExtensionPlugin } from \"@webiny/handler\";\nimport type { AssetDeliveryParams } from \"~/assetDelivery/types.js\";\nimport { createS3AssetDeliveryFeature } from \"~/assetDelivery/feature.js\";\n\nexport const assetDeliveryConfig = (params: AssetDeliveryParams) => {\n const feature = createS3AssetDeliveryFeature(params);\n\n return [\n createRegisterExtensionPlugin(context => {\n feature.register(context.container);\n })\n ];\n};\n"],"names":["assetDeliveryConfig","params","feature","createS3AssetDeliveryFeature","createRegisterExtensionPlugin","context"],"mappings":";;AAIO,MAAMA,sBAAsB,CAACC;IAChC,MAAMC,UAAUC,6BAA6BF;IAE7C,OAAO;QACHG,8BAA8BC,CAAAA;YAC1BH,QAAQ,QAAQ,CAACG,QAAQ,SAAS;QACtC;KACH;AACL"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import sharp from "sharp";
|
|
2
1
|
import { AssetTransformationStrategy } from "@webiny/api-file-manager/features/assetDelivery/abstractions.js";
|
|
3
2
|
import { WidthCollection } from "./transformation/WidthCollection.js";
|
|
4
3
|
import { CallableContentsReader } from "./transformation/CallableContentsReader.js";
|
|
5
4
|
import { AssetKeyGenerator } from "./transformation/AssetKeyGenerator.js";
|
|
6
5
|
import { S3AssetDeliveryConfig, S3Bucket, S3Client } from "../abstractions.js";
|
|
7
6
|
import * as __rspack_external__transformation_utils_js_9257ec9a from "./transformation/utils.js";
|
|
7
|
+
let sharpCache;
|
|
8
|
+
async function loadSharp() {
|
|
9
|
+
if (!sharpCache) sharpCache = (await import("sharp")).default;
|
|
10
|
+
return sharpCache;
|
|
11
|
+
}
|
|
8
12
|
class SharpTransform {
|
|
9
13
|
constructor(s3, bucket, config){
|
|
10
14
|
this.s3 = s3;
|
|
@@ -47,6 +51,7 @@ class SharpTransform {
|
|
|
47
51
|
const width = widths.getClosestOrMax(options.width);
|
|
48
52
|
console.log(`Resize the asset (width: ${width})`);
|
|
49
53
|
const buffer = await optimizedImage.getContents();
|
|
54
|
+
const sharp = await loadSharp();
|
|
50
55
|
const transformedBuffer = await sharp(buffer, {
|
|
51
56
|
animated: this.isAssetAnimated(asset)
|
|
52
57
|
}).withMetadata().resize({
|
|
@@ -107,7 +112,7 @@ class SharpTransform {
|
|
|
107
112
|
console.log(`No optimizations defined for ${asset.getContentType()}`);
|
|
108
113
|
return asset;
|
|
109
114
|
}
|
|
110
|
-
const optimizedBuffer = await optimization(buffer)
|
|
115
|
+
const optimizedBuffer = await optimization(buffer);
|
|
111
116
|
console.log("Optimized asset size", optimizedBuffer.length);
|
|
112
117
|
const newAsset = asset.withProps({
|
|
113
118
|
size: optimizedBuffer.length
|
|
@@ -128,7 +133,8 @@ class SharpTransform {
|
|
|
128
133
|
"webp"
|
|
129
134
|
].includes(asset.getExtension());
|
|
130
135
|
}
|
|
131
|
-
optimizePng(buffer) {
|
|
136
|
+
async optimizePng(buffer) {
|
|
137
|
+
const sharp = await loadSharp();
|
|
132
138
|
return sharp(buffer).resize({
|
|
133
139
|
width: 2560,
|
|
134
140
|
withoutEnlargement: true,
|
|
@@ -137,16 +143,17 @@ class SharpTransform {
|
|
|
137
143
|
compressionLevel: 9,
|
|
138
144
|
adaptiveFiltering: true,
|
|
139
145
|
force: true
|
|
140
|
-
}).withMetadata();
|
|
146
|
+
}).withMetadata().toBuffer();
|
|
141
147
|
}
|
|
142
|
-
optimizeJpeg(buffer) {
|
|
148
|
+
async optimizeJpeg(buffer) {
|
|
149
|
+
const sharp = await loadSharp();
|
|
143
150
|
return sharp(buffer).resize({
|
|
144
151
|
width: 2560,
|
|
145
152
|
withoutEnlargement: true,
|
|
146
153
|
fit: "inside"
|
|
147
154
|
}).withMetadata().toFormat("jpeg", {
|
|
148
155
|
quality: 90
|
|
149
|
-
});
|
|
156
|
+
}).toBuffer();
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
const SharpTransformImpl = AssetTransformationStrategy.createImplementation({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetDelivery/s3/SharpTransform.js","sources":["../../../src/assetDelivery/s3/SharpTransform.ts"],"sourcesContent":["import sharp, { type Sharp } from \"sharp\";\nimport type { S3 } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport type {\n Asset,\n AssetRequest,\n AssetRequestOptions,\n AssetTransformationStrategy\n} from \"@webiny/api-file-manager\";\nimport { AssetTransformationStrategy as AssetTransformationStrategyAbstraction } from \"@webiny/api-file-manager/features/assetDelivery/abstractions.js\";\nimport { WidthCollection } from \"./transformation/WidthCollection.js\";\nimport * as utils from \"./transformation/utils.js\";\nimport { CallableContentsReader } from \"./transformation/CallableContentsReader.js\";\nimport { AssetKeyGenerator } from \"./transformation/AssetKeyGenerator.js\";\nimport { S3Client, S3Bucket, S3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\nimport type { IS3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\n\nexport class SharpTransform implements AssetTransformationStrategy {\n private readonly s3: S3;\n private readonly bucket: string;\n private readonly imageResizeWidths: number[];\n\n constructor(s3: S3, bucket: string, config: IS3AssetDeliveryConfig) {\n this.s3 = s3;\n this.bucket = bucket;\n this.imageResizeWidths = config.imageResizeWidths;\n }\n\n async transform(assetRequest: AssetRequest, asset: Asset): Promise<Asset> {\n if (!utils.SUPPORTED_TRANSFORMABLE_IMAGES.includes(asset.getExtension())) {\n console.log(\n `Transformations/optimizations of ${asset.getContentType()} assets are not supported. Skipping.`\n );\n return asset;\n }\n\n // oxlint-disable-next-line typescript/no-unused-vars\n const { original, ...options } = assetRequest.getOptions();\n\n const transformedAsset = asset.clone();\n\n if (Object.keys(options).length > 0) {\n return this.transformAsset(transformedAsset, options);\n }\n\n return this.optimizeAsset(transformedAsset);\n }\n\n private async transformAsset(asset: Asset, options: Omit<AssetRequestOptions, \"original\">) {\n if (options.width) {\n const assetKey = new AssetKeyGenerator(asset);\n const transformedAssetKey = assetKey.getTransformedImageKey(options);\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: transformedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => buffer));\n\n console.log(`Return a previously transformed asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n } catch {\n const optimizedImage = await this.optimizeAsset(asset);\n\n const widths = new WidthCollection(this.imageResizeWidths);\n const width = widths.getClosestOrMax(options.width);\n\n console.log(`Resize the asset (width: ${width})`);\n const buffer = await optimizedImage.getContents();\n const transformedBuffer = await sharp(buffer, {\n animated: this.isAssetAnimated(asset)\n })\n .withMetadata()\n .resize({ width, withoutEnlargement: true })\n .toBuffer();\n\n const newAsset = asset.withProps({ size: transformedBuffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => transformedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: transformedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n console.log(`Return the resized asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n }\n }\n\n return asset;\n }\n\n private async optimizeAsset(asset: Asset) {\n console.log(\"Optimize asset\", {\n id: asset.getId(),\n key: asset.getKey(),\n size: asset.getSize(),\n type: asset.getContentType()\n });\n\n const assetKey = new AssetKeyGenerator(asset);\n const optimizedAssetKey = assetKey.getOptimizedImageKey();\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n console.log(\"Return a previously optimized asset\", optimizedAssetKey);\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => buffer));\n\n return newAsset;\n } catch {\n console.log(\"Create an optimized version of the original asset\", asset.getKey());\n const buffer = await asset.getContents();\n\n const optimizationMap: Record<string, ((buffer: Buffer) => Sharp) | undefined> = {\n \"image/png\": (buffer: Buffer) => this.optimizePng(buffer),\n \"image/jpeg\": (buffer: Buffer) => this.optimizeJpeg(buffer),\n \"image/jpg\": (buffer: Buffer) => this.optimizeJpeg(buffer)\n };\n\n const optimization = optimizationMap[asset.getContentType()];\n\n if (!optimization) {\n console.log(`No optimizations defined for ${asset.getContentType()}`);\n return asset;\n }\n\n const optimizedBuffer = await optimization(buffer).toBuffer();\n\n console.log(\"Optimized asset size\", optimizedBuffer.length);\n\n const newAsset = asset.withProps({ size: optimizedBuffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => optimizedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n return newAsset;\n }\n }\n\n private isAssetAnimated(asset: Asset) {\n return [\"gif\", \"webp\"].includes(asset.getExtension());\n }\n\n private optimizePng(buffer: Buffer) {\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })\n .withMetadata();\n }\n\n private optimizeJpeg(buffer: Buffer) {\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .withMetadata()\n .toFormat(\"jpeg\", { quality: 90 });\n }\n}\n\nexport const SharpTransformImpl = AssetTransformationStrategyAbstraction.createImplementation({\n implementation: SharpTransform,\n dependencies: [S3Client, S3Bucket, S3AssetDeliveryConfig]\n});\n"],"names":["SharpTransform","s3","bucket","config","assetRequest","asset","utils","console","original","options","transformedAsset","Object","assetKey","AssetKeyGenerator","transformedAssetKey","Body","Error","buffer","Buffer","newAsset","CallableContentsReader","optimizedImage","widths","WidthCollection","width","transformedBuffer","sharp","optimizedAssetKey","optimizationMap","optimization","optimizedBuffer","SharpTransformImpl","AssetTransformationStrategyAbstraction","S3Client","S3Bucket","S3AssetDeliveryConfig"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"assetDelivery/s3/SharpTransform.js","sources":["../../../src/assetDelivery/s3/SharpTransform.ts"],"sourcesContent":["import type { S3 } from \"@webiny/aws-sdk/client-s3/index.js\";\nimport type {\n Asset,\n AssetRequest,\n AssetRequestOptions,\n AssetTransformationStrategy\n} from \"@webiny/api-file-manager\";\nimport { AssetTransformationStrategy as AssetTransformationStrategyAbstraction } from \"@webiny/api-file-manager/features/assetDelivery/abstractions.js\";\nimport { WidthCollection } from \"./transformation/WidthCollection.js\";\nimport * as utils from \"./transformation/utils.js\";\nimport { CallableContentsReader } from \"./transformation/CallableContentsReader.js\";\nimport { AssetKeyGenerator } from \"./transformation/AssetKeyGenerator.js\";\nimport { S3Client, S3Bucket, S3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\nimport type { IS3AssetDeliveryConfig } from \"~/assetDelivery/abstractions.js\";\n\nimport type sharpType from \"sharp\";\ntype SharpFn = typeof sharpType;\nlet sharpCache: SharpFn | undefined;\nasync function loadSharp(): Promise<SharpFn> {\n if (!sharpCache) {\n sharpCache = (await import(\"sharp\")).default as SharpFn;\n }\n return sharpCache;\n}\n\nexport class SharpTransform implements AssetTransformationStrategy {\n private readonly s3: S3;\n private readonly bucket: string;\n private readonly imageResizeWidths: number[];\n\n constructor(s3: S3, bucket: string, config: IS3AssetDeliveryConfig) {\n this.s3 = s3;\n this.bucket = bucket;\n this.imageResizeWidths = config.imageResizeWidths;\n }\n\n async transform(assetRequest: AssetRequest, asset: Asset): Promise<Asset> {\n if (!utils.SUPPORTED_TRANSFORMABLE_IMAGES.includes(asset.getExtension())) {\n console.log(\n `Transformations/optimizations of ${asset.getContentType()} assets are not supported. Skipping.`\n );\n return asset;\n }\n\n // oxlint-disable-next-line typescript/no-unused-vars\n const { original, ...options } = assetRequest.getOptions();\n\n const transformedAsset = asset.clone();\n\n if (Object.keys(options).length > 0) {\n return this.transformAsset(transformedAsset, options);\n }\n\n return this.optimizeAsset(transformedAsset);\n }\n\n private async transformAsset(asset: Asset, options: Omit<AssetRequestOptions, \"original\">) {\n if (options.width) {\n const assetKey = new AssetKeyGenerator(asset);\n const transformedAssetKey = assetKey.getTransformedImageKey(options);\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: transformedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => buffer));\n\n console.log(`Return a previously transformed asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n } catch {\n const optimizedImage = await this.optimizeAsset(asset);\n\n const widths = new WidthCollection(this.imageResizeWidths);\n const width = widths.getClosestOrMax(options.width);\n\n console.log(`Resize the asset (width: ${width})`);\n const buffer = await optimizedImage.getContents();\n const sharp = await loadSharp();\n const transformedBuffer = await sharp(buffer, {\n animated: this.isAssetAnimated(asset)\n })\n .withMetadata()\n .resize({ width, withoutEnlargement: true })\n .toBuffer();\n\n const newAsset = asset.withProps({ size: transformedBuffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => transformedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: transformedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n console.log(`Return the resized asset`, {\n key: transformedAssetKey,\n size: newAsset.getSize()\n });\n\n return newAsset;\n }\n }\n\n return asset;\n }\n\n private async optimizeAsset(asset: Asset) {\n console.log(\"Optimize asset\", {\n id: asset.getId(),\n key: asset.getKey(),\n size: asset.getSize(),\n type: asset.getContentType()\n });\n\n const assetKey = new AssetKeyGenerator(asset);\n const optimizedAssetKey = assetKey.getOptimizedImageKey();\n\n try {\n const { Body } = await this.s3.getObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey\n });\n\n if (!Body) {\n throw new Error(`Missing image body!`);\n }\n\n console.log(\"Return a previously optimized asset\", optimizedAssetKey);\n\n const buffer = Buffer.from(await Body.transformToByteArray());\n\n const newAsset = asset.withProps({ size: buffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => buffer));\n\n return newAsset;\n } catch {\n console.log(\"Create an optimized version of the original asset\", asset.getKey());\n const buffer = await asset.getContents();\n\n const optimizationMap: Record<\n string,\n ((buffer: Buffer) => Promise<Buffer>) | undefined\n > = {\n \"image/png\": (buffer: Buffer) => this.optimizePng(buffer),\n \"image/jpeg\": (buffer: Buffer) => this.optimizeJpeg(buffer),\n \"image/jpg\": (buffer: Buffer) => this.optimizeJpeg(buffer)\n };\n\n const optimization = optimizationMap[asset.getContentType()];\n\n if (!optimization) {\n console.log(`No optimizations defined for ${asset.getContentType()}`);\n return asset;\n }\n\n const optimizedBuffer = await optimization(buffer);\n\n console.log(\"Optimized asset size\", optimizedBuffer.length);\n\n const newAsset = asset.withProps({ size: optimizedBuffer.length });\n newAsset.setContentsReader(new CallableContentsReader(() => optimizedBuffer));\n\n await this.s3.putObject({\n Bucket: this.bucket,\n Key: optimizedAssetKey,\n ContentType: newAsset.getContentType(),\n Body: await newAsset.getContents()\n });\n\n return newAsset;\n }\n }\n\n private isAssetAnimated(asset: Asset) {\n return [\"gif\", \"webp\"].includes(asset.getExtension());\n }\n\n private async optimizePng(buffer: Buffer): Promise<Buffer> {\n const sharp = await loadSharp();\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })\n .withMetadata()\n .toBuffer();\n }\n\n private async optimizeJpeg(buffer: Buffer): Promise<Buffer> {\n const sharp = await loadSharp();\n return sharp(buffer)\n .resize({ width: 2560, withoutEnlargement: true, fit: \"inside\" })\n .withMetadata()\n .toFormat(\"jpeg\", { quality: 90 })\n .toBuffer();\n }\n}\n\nexport const SharpTransformImpl = AssetTransformationStrategyAbstraction.createImplementation({\n implementation: SharpTransform,\n dependencies: [S3Client, S3Bucket, S3AssetDeliveryConfig]\n});\n"],"names":["sharpCache","loadSharp","SharpTransform","s3","bucket","config","assetRequest","asset","utils","console","original","options","transformedAsset","Object","assetKey","AssetKeyGenerator","transformedAssetKey","Body","Error","buffer","Buffer","newAsset","CallableContentsReader","optimizedImage","widths","WidthCollection","width","sharp","transformedBuffer","optimizedAssetKey","optimizationMap","optimization","optimizedBuffer","SharpTransformImpl","AssetTransformationStrategyAbstraction","S3Client","S3Bucket","S3AssetDeliveryConfig"],"mappings":"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"}
|
|
@@ -1,2 +1,22 @@
|
|
|
1
|
+
import type { Container } from "@webiny/feature/api";
|
|
2
|
+
import type { EventBridgeEvent } from "@webiny/aws-sdk/types/index.js";
|
|
3
|
+
import { EventBridgeEventHandler, type EventBridgeResult } from "@webiny/event-handler-aws/abstractions/handlers/EventBridgeEventHandler.js";
|
|
4
|
+
import type { ITenantContext } from "@webiny/api-core/features/tenancy/TenantContext/abstractions.js";
|
|
5
|
+
import type { IGetTenantByIdUseCase } from "@webiny/api-core/features/tenancy/GetTenantById/abstractions.js";
|
|
6
|
+
import type { EventContext, NextFunction } from "@webiny/event-handler-core";
|
|
1
7
|
import type { GuardDutyEvent } from "./types.js";
|
|
2
|
-
|
|
8
|
+
declare class ThreatDetectionEventBridgeLambdaHandlerImpl implements EventBridgeEventHandler.Interface {
|
|
9
|
+
private container;
|
|
10
|
+
private tenantCtx;
|
|
11
|
+
private getTenantById;
|
|
12
|
+
constructor(container: Container, tenantCtx: ITenantContext, getTenantById: IGetTenantByIdUseCase);
|
|
13
|
+
execute(eventCtx: EventContext<EventBridgeEvent<string, GuardDutyEvent>>, _next: NextFunction): Promise<EventBridgeResult>;
|
|
14
|
+
}
|
|
15
|
+
export declare const ThreatDetectionEventBridgeLambdaHandler: typeof ThreatDetectionEventBridgeLambdaHandlerImpl & {
|
|
16
|
+
__abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-aws/abstractions/handlers/EventBridgeEventHandler.js").IEventBridgeEventHandler>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Use ThreatDetectionEventBridgeLambdaHandler instead.
|
|
20
|
+
*/
|
|
21
|
+
export declare const createThreatDetectionEventHandler: () => never[];
|
|
22
|
+
export {};
|
|
@@ -1,37 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { EventBridgeEventHandler } from "@webiny/event-handler-aws/abstractions/handlers/EventBridgeEventHandler.js";
|
|
2
|
+
import { RequestContainer } from "@webiny/event-handler-core";
|
|
3
|
+
import { TenantContext } from "@webiny/api-core/features/tenancy/TenantContext/index.js";
|
|
4
|
+
import { GetTenantByIdUseCase } from "@webiny/api-core/features/tenancy/GetTenantById/index.js";
|
|
5
|
+
import { WcpContext } from "@webiny/api-core/features/wcp/WcpContext/index.js";
|
|
3
6
|
import { GlobalKeyValueStore } from "@webiny/api-core/features/keyValueStore/index.js";
|
|
4
7
|
import { processThreatScanResult } from "./processThreatScanResult.js";
|
|
5
8
|
import { ObjectKey } from "./ObjectKey.js";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const DETAIL_TYPE = "GuardDuty Malware Protection Object Scan Result";
|
|
10
|
+
class ThreatDetectionEventBridgeLambdaHandlerImpl {
|
|
11
|
+
constructor(container, tenantCtx, getTenantById){
|
|
12
|
+
this.container = container;
|
|
13
|
+
this.tenantCtx = tenantCtx;
|
|
14
|
+
this.getTenantById = getTenantById;
|
|
15
|
+
}
|
|
16
|
+
async execute(eventCtx, _next) {
|
|
17
|
+
const payload = eventCtx.event;
|
|
18
|
+
if (payload["detail-type"] !== DETAIL_TYPE) return {
|
|
19
|
+
success: true
|
|
20
|
+
};
|
|
21
|
+
if (!this.container.resolve(WcpContext).canUseFileManagerThreatDetection()) return {
|
|
22
|
+
success: true
|
|
23
|
+
};
|
|
11
24
|
const objectKey = payload.detail.s3ObjectDetails.objectKey;
|
|
12
|
-
const keyValueStore =
|
|
25
|
+
const keyValueStore = this.container.resolve(GlobalKeyValueStore);
|
|
13
26
|
try {
|
|
14
27
|
const fileId = ObjectKey.from(objectKey).id();
|
|
15
28
|
const result = await keyValueStore.get(`FileManager/File/${fileId}/Metadata`);
|
|
16
|
-
if (result.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
29
|
+
if (result.isOk()) {
|
|
30
|
+
const tenantResult = await this.getTenantById.execute(result.value.tenant);
|
|
31
|
+
if (tenantResult.isOk()) this.tenantCtx.setTenant(tenantResult.value);
|
|
32
|
+
}
|
|
21
33
|
} catch {}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
await processThreatScanResult({
|
|
35
|
+
container: this.container
|
|
36
|
+
}, payload.detail);
|
|
37
|
+
return {
|
|
38
|
+
success: true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const ThreatDetectionEventBridgeLambdaHandler = EventBridgeEventHandler.createImplementation({
|
|
43
|
+
implementation: ThreatDetectionEventBridgeLambdaHandlerImpl,
|
|
44
|
+
dependencies: [
|
|
45
|
+
RequestContainer,
|
|
46
|
+
TenantContext,
|
|
47
|
+
GetTenantByIdUseCase
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
const createThreatDetectionEventHandler = ()=>[];
|
|
51
|
+
export { ThreatDetectionEventBridgeLambdaHandler, createThreatDetectionEventHandler };
|
|
36
52
|
|
|
37
53
|
//# sourceMappingURL=createThreatDetectionEventHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetDelivery/threatDetection/createThreatDetectionEventHandler.js","sources":["../../../src/assetDelivery/threatDetection/createThreatDetectionEventHandler.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"assetDelivery/threatDetection/createThreatDetectionEventHandler.js","sources":["../../../src/assetDelivery/threatDetection/createThreatDetectionEventHandler.ts"],"sourcesContent":["import type { Container } from \"@webiny/feature/api\";\nimport type { EventBridgeEvent } from \"@webiny/aws-sdk/types/index.js\";\nimport {\n EventBridgeEventHandler,\n type EventBridgeResult\n} from \"@webiny/event-handler-aws/abstractions/handlers/EventBridgeEventHandler.js\";\nimport { RequestContainer } from \"@webiny/event-handler-core\";\nimport { TenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/index.js\";\nimport { GetTenantByIdUseCase } from \"@webiny/api-core/features/tenancy/GetTenantById/index.js\";\nimport type { ITenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/abstractions.js\";\nimport type { IGetTenantByIdUseCase } from \"@webiny/api-core/features/tenancy/GetTenantById/abstractions.js\";\nimport type { EventContext, NextFunction } from \"@webiny/event-handler-core\";\nimport type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { WcpContext } from \"@webiny/api-core/features/wcp/WcpContext/index.js\";\nimport { GlobalKeyValueStore } from \"@webiny/api-core/features/keyValueStore/index.js\";\nimport { processThreatScanResult } from \"./processThreatScanResult.js\";\nimport { ObjectKey } from \"./ObjectKey.js\";\nimport type { GuardDutyEvent } from \"./types.js\";\n\nconst DETAIL_TYPE = \"GuardDuty Malware Protection Object Scan Result\";\n\nclass ThreatDetectionEventBridgeLambdaHandlerImpl implements EventBridgeEventHandler.Interface {\n constructor(\n private container: Container,\n private tenantCtx: ITenantContext,\n private getTenantById: IGetTenantByIdUseCase\n ) {}\n\n async execute(\n eventCtx: EventContext<EventBridgeEvent<string, GuardDutyEvent>>,\n _next: NextFunction\n ): Promise<EventBridgeResult> {\n const payload = eventCtx.event;\n if (payload[\"detail-type\"] !== DETAIL_TYPE) {\n return { success: true };\n }\n\n if (!this.container.resolve(WcpContext).canUseFileManagerThreatDetection()) {\n return { success: true };\n }\n\n const objectKey = payload.detail.s3ObjectDetails.objectKey;\n const keyValueStore = this.container.resolve(GlobalKeyValueStore);\n\n try {\n const fileId = ObjectKey.from(objectKey).id();\n const result = await keyValueStore.get<{ tenant: string }>(\n `FileManager/File/${fileId}/Metadata`\n );\n\n if (result.isOk()) {\n const tenantResult = await this.getTenantById.execute(result.value.tenant);\n if (tenantResult.isOk()) {\n this.tenantCtx.setTenant(tenantResult.value);\n }\n }\n } catch {\n // If metadata can't be loaded, ignore — likely a rendition file.\n }\n\n await processThreatScanResult(\n { container: this.container } as unknown as ApiCoreContext,\n payload.detail\n );\n return { success: true };\n }\n}\n\nexport const ThreatDetectionEventBridgeLambdaHandler = EventBridgeEventHandler.createImplementation(\n {\n implementation: ThreatDetectionEventBridgeLambdaHandlerImpl,\n dependencies: [RequestContainer, TenantContext, GetTenantByIdUseCase]\n }\n);\n\n/**\n * @deprecated Use ThreatDetectionEventBridgeLambdaHandler instead.\n */\nexport const createThreatDetectionEventHandler = () => {\n return [];\n};\n"],"names":["DETAIL_TYPE","ThreatDetectionEventBridgeLambdaHandlerImpl","container","tenantCtx","getTenantById","eventCtx","_next","payload","WcpContext","objectKey","keyValueStore","GlobalKeyValueStore","fileId","ObjectKey","result","tenantResult","processThreatScanResult","ThreatDetectionEventBridgeLambdaHandler","EventBridgeEventHandler","RequestContainer","TenantContext","GetTenantByIdUseCase","createThreatDetectionEventHandler"],"mappings":";;;;;;;;AAmBA,MAAMA,cAAc;AAEpB,MAAMC;IACF,YACYC,SAAoB,EACpBC,SAAyB,EACzBC,aAAoC,CAC9C;aAHUF,SAAS,GAATA;aACAC,SAAS,GAATA;aACAC,aAAa,GAAbA;IACT;IAEH,MAAM,QACFC,QAAgE,EAChEC,KAAmB,EACO;QAC1B,MAAMC,UAAUF,SAAS,KAAK;QAC9B,IAAIE,OAAO,CAAC,cAAc,KAAKP,aAC3B,OAAO;YAAE,SAAS;QAAK;QAG3B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAACQ,YAAY,gCAAgC,IACpE,OAAO;YAAE,SAAS;QAAK;QAG3B,MAAMC,YAAYF,QAAQ,MAAM,CAAC,eAAe,CAAC,SAAS;QAC1D,MAAMG,gBAAgB,IAAI,CAAC,SAAS,CAAC,OAAO,CAACC;QAE7C,IAAI;YACA,MAAMC,SAASC,UAAU,IAAI,CAACJ,WAAW,EAAE;YAC3C,MAAMK,SAAS,MAAMJ,cAAc,GAAG,CAClC,CAAC,iBAAiB,EAAEE,OAAO,SAAS,CAAC;YAGzC,IAAIE,OAAO,IAAI,IAAI;gBACf,MAAMC,eAAe,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAACD,OAAO,KAAK,CAAC,MAAM;gBACzE,IAAIC,aAAa,IAAI,IACjB,IAAI,CAAC,SAAS,CAAC,SAAS,CAACA,aAAa,KAAK;YAEnD;QACJ,EAAE,OAAM,CAER;QAEA,MAAMC,wBACF;YAAE,WAAW,IAAI,CAAC,SAAS;QAAC,GAC5BT,QAAQ,MAAM;QAElB,OAAO;YAAE,SAAS;QAAK;IAC3B;AACJ;AAEO,MAAMU,0CAA0CC,wBAAwB,oBAAoB,CAC/F;IACI,gBAAgBjB;IAChB,cAAc;QAACkB;QAAkBC;QAAeC;KAAqB;AACzE;AAMG,MAAMC,oCAAoC,IACtC,EAAE"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
2
2
|
import { DeleteFileUseCase } from "@webiny/api-file-manager/features/file/DeleteFile/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { WebsocketsListConnectionsUseCase, WebsocketsSendToConnectionsUseCase } from "@webiny/api-websockets/exports/api.js";
|
|
4
4
|
import { ObjectKey } from "./ObjectKey.js";
|
|
5
5
|
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
6
|
+
import { IdentityContext } from "@webiny/api-core/features/security/IdentityContext/abstractions.js";
|
|
6
7
|
const processThreatScanResult = async (context, eventDetail)=>{
|
|
7
|
-
const
|
|
8
|
+
const listConnections = context.container.resolve(WebsocketsListConnectionsUseCase);
|
|
9
|
+
const sendToConnections = context.container.resolve(WebsocketsSendToConnectionsUseCase);
|
|
8
10
|
const getFile = context.container.resolve(GetFileUseCase);
|
|
9
11
|
const updateFile = context.container.resolve(UpdateFileUseCase);
|
|
10
12
|
const deleteFile = context.container.resolve(DeleteFileUseCase);
|
|
11
|
-
await context.
|
|
13
|
+
await context.container.resolve(IdentityContext).withoutAuthorization(async ()=>{
|
|
12
14
|
const scanStatus = eventDetail.scanResultDetails.scanResultStatus;
|
|
13
15
|
const s3Object = eventDetail.s3ObjectDetails;
|
|
14
16
|
const fileId = ObjectKey.from(s3Object.objectKey).id();
|
|
@@ -16,7 +18,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
|
|
|
16
18
|
if (fileResult.isFail()) return;
|
|
17
19
|
const file = fileResult.value;
|
|
18
20
|
let allConnections = [];
|
|
19
|
-
const connectionsResult = await
|
|
21
|
+
const connectionsResult = await listConnections.execute();
|
|
20
22
|
if (connectionsResult.isOk()) allConnections = connectionsResult.value;
|
|
21
23
|
if ("NO_THREATS_FOUND" === scanStatus) {
|
|
22
24
|
const newTags = file.tags.filter((tag)=>"threatScanInProgress" !== tag);
|
|
@@ -24,7 +26,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
|
|
|
24
26
|
id: file.id,
|
|
25
27
|
tags: newTags
|
|
26
28
|
});
|
|
27
|
-
await
|
|
29
|
+
await sendToConnections.execute(allConnections, {
|
|
28
30
|
action: "fm.threatScan.noThreatFound",
|
|
29
31
|
data: {
|
|
30
32
|
id: file.id,
|
|
@@ -35,7 +37,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
|
|
|
35
37
|
}
|
|
36
38
|
if ("THREATS_FOUND" === scanStatus) {
|
|
37
39
|
await deleteFile.execute(file.id);
|
|
38
|
-
await
|
|
40
|
+
await sendToConnections.execute(allConnections, {
|
|
39
41
|
action: "fm.threatScan.threatDetected",
|
|
40
42
|
data: {
|
|
41
43
|
id: file.id,
|
|
@@ -45,7 +47,7 @@ const processThreatScanResult = async (context, eventDetail)=>{
|
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
await deleteFile.execute(file.id);
|
|
48
|
-
await
|
|
50
|
+
await sendToConnections.execute(allConnections, {
|
|
49
51
|
action: "fm.threatScan.unsupported",
|
|
50
52
|
data: {
|
|
51
53
|
id: file.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetDelivery/threatDetection/processThreatScanResult.js","sources":["../../../src/assetDelivery/threatDetection/processThreatScanResult.ts"],"sourcesContent":["import type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { DeleteFileUseCase } from \"@webiny/api-file-manager/features/file/DeleteFile/index.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"assetDelivery/threatDetection/processThreatScanResult.js","sources":["../../../src/assetDelivery/threatDetection/processThreatScanResult.ts"],"sourcesContent":["import type { ApiCoreContext } from \"@webiny/api-core/types/core.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { DeleteFileUseCase } from \"@webiny/api-file-manager/features/file/DeleteFile/index.js\";\nimport {\n WebsocketsListConnectionsUseCase,\n WebsocketsSendToConnectionsUseCase,\n ConnectionRegistry\n} from \"@webiny/api-websockets/exports/api.js\";\nimport type { GuardDutyEvent } from \"./types.js\";\nimport { ObjectKey } from \"./ObjectKey.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport { IdentityContext } from \"@webiny/api-core/features/security/IdentityContext/abstractions.js\";\n\nexport const processThreatScanResult = async (\n context: ApiCoreContext,\n eventDetail: GuardDutyEvent\n) => {\n const listConnections = context.container.resolve(WebsocketsListConnectionsUseCase);\n const sendToConnections = context.container.resolve(WebsocketsSendToConnectionsUseCase);\n const getFile = context.container.resolve(GetFileUseCase);\n const updateFile = context.container.resolve(UpdateFileUseCase);\n const deleteFile = context.container.resolve(DeleteFileUseCase);\n\n await context.container.resolve(IdentityContext).withoutAuthorization(async () => {\n const scanStatus = eventDetail.scanResultDetails.scanResultStatus;\n const s3Object = eventDetail.s3ObjectDetails;\n\n const fileId = ObjectKey.from(s3Object.objectKey).id();\n const fileResult = await getFile.execute(fileId);\n\n if (fileResult.isFail()) {\n return;\n }\n\n const file = fileResult.value;\n\n let allConnections: ConnectionRegistry.Data[] = [];\n const connectionsResult = await listConnections.execute();\n if (connectionsResult.isOk()) {\n allConnections = connectionsResult.value;\n }\n\n if (scanStatus === \"NO_THREATS_FOUND\") {\n const newTags = file.tags.filter(tag => tag !== \"threatScanInProgress\");\n await updateFile.execute({\n id: file.id,\n tags: newTags\n });\n\n await sendToConnections.execute(allConnections, {\n action: \"fm.threatScan.noThreatFound\",\n data: {\n id: file.id,\n tags: newTags\n }\n });\n\n return;\n }\n\n if (scanStatus === \"THREATS_FOUND\") {\n // Delete the infected file.\n await deleteFile.execute(file.id);\n\n await sendToConnections.execute(allConnections, {\n action: \"fm.threatScan.threatDetected\",\n data: {\n id: file.id,\n name: file.name\n }\n });\n\n return;\n }\n\n // For all other outcomes, we delete the file, until better logic is implemented.\n await deleteFile.execute(file.id);\n\n await sendToConnections.execute(allConnections, {\n action: \"fm.threatScan.unsupported\",\n data: {\n id: file.id,\n name: file.name\n }\n });\n });\n};\n"],"names":["processThreatScanResult","context","eventDetail","listConnections","WebsocketsListConnectionsUseCase","sendToConnections","WebsocketsSendToConnectionsUseCase","getFile","GetFileUseCase","updateFile","UpdateFileUseCase","deleteFile","DeleteFileUseCase","IdentityContext","scanStatus","s3Object","fileId","ObjectKey","fileResult","file","allConnections","connectionsResult","newTags","tag"],"mappings":";;;;;;AAaO,MAAMA,0BAA0B,OACnCC,SACAC;IAEA,MAAMC,kBAAkBF,QAAQ,SAAS,CAAC,OAAO,CAACG;IAClD,MAAMC,oBAAoBJ,QAAQ,SAAS,CAAC,OAAO,CAACK;IACpD,MAAMC,UAAUN,QAAQ,SAAS,CAAC,OAAO,CAACO;IAC1C,MAAMC,aAAaR,QAAQ,SAAS,CAAC,OAAO,CAACS;IAC7C,MAAMC,aAAaV,QAAQ,SAAS,CAAC,OAAO,CAACW;IAE7C,MAAMX,QAAQ,SAAS,CAAC,OAAO,CAACY,iBAAiB,oBAAoB,CAAC;QAClE,MAAMC,aAAaZ,YAAY,iBAAiB,CAAC,gBAAgB;QACjE,MAAMa,WAAWb,YAAY,eAAe;QAE5C,MAAMc,SAASC,UAAU,IAAI,CAACF,SAAS,SAAS,EAAE,EAAE;QACpD,MAAMG,aAAa,MAAMX,QAAQ,OAAO,CAACS;QAEzC,IAAIE,WAAW,MAAM,IACjB;QAGJ,MAAMC,OAAOD,WAAW,KAAK;QAE7B,IAAIE,iBAA4C,EAAE;QAClD,MAAMC,oBAAoB,MAAMlB,gBAAgB,OAAO;QACvD,IAAIkB,kBAAkB,IAAI,IACtBD,iBAAiBC,kBAAkB,KAAK;QAG5C,IAAIP,AAAe,uBAAfA,YAAmC;YACnC,MAAMQ,UAAUH,KAAK,IAAI,CAAC,MAAM,CAACI,CAAAA,MAAOA,AAAQ,2BAARA;YACxC,MAAMd,WAAW,OAAO,CAAC;gBACrB,IAAIU,KAAK,EAAE;gBACX,MAAMG;YACV;YAEA,MAAMjB,kBAAkB,OAAO,CAACe,gBAAgB;gBAC5C,QAAQ;gBACR,MAAM;oBACF,IAAID,KAAK,EAAE;oBACX,MAAMG;gBACV;YACJ;YAEA;QACJ;QAEA,IAAIR,AAAe,oBAAfA,YAAgC;YAEhC,MAAMH,WAAW,OAAO,CAACQ,KAAK,EAAE;YAEhC,MAAMd,kBAAkB,OAAO,CAACe,gBAAgB;gBAC5C,QAAQ;gBACR,MAAM;oBACF,IAAID,KAAK,EAAE;oBACX,MAAMA,KAAK,IAAI;gBACnB;YACJ;YAEA;QACJ;QAGA,MAAMR,WAAW,OAAO,CAACQ,KAAK,EAAE;QAEhC,MAAMd,kBAAkB,OAAO,CAACe,gBAAgB;YAC5C,QAAQ;YACR,MAAM;gBACF,IAAID,KAAK,EAAE;gBACX,MAAMA,KAAK,IAAI;YACnB;QACJ;IACJ;AACJ"}
|
|
@@ -23,7 +23,7 @@ declare class DeleteS3FolderTask implements TaskDefinition.Interface<DeleteS3Fol
|
|
|
23
23
|
description: string;
|
|
24
24
|
maxIterations: number;
|
|
25
25
|
isPrivate: boolean;
|
|
26
|
-
readonly selfCleanup: ("
|
|
26
|
+
readonly selfCleanup: ("onAbort" | "onSuccess")[];
|
|
27
27
|
run({ input, controller }: TaskDefinition.RunParams<DeleteS3FolderInput>): Promise<TaskDefinition.Result<DeleteS3FolderInput>>;
|
|
28
28
|
}
|
|
29
29
|
export declare const DeleteS3FolderTaskDefinition: typeof DeleteS3FolderTask & {
|
|
@@ -13,7 +13,7 @@ declare class ExtractMetadataTask implements TaskDefinition.Interface<ExtractMet
|
|
|
13
13
|
maxIterations: number;
|
|
14
14
|
isPrivate: boolean;
|
|
15
15
|
databaseLogs: boolean;
|
|
16
|
-
selfCleanup: ("
|
|
16
|
+
selfCleanup: ("onAbort" | "onSuccess")[];
|
|
17
17
|
constructor(keyValueStore: GlobalKeyValueStore.Interface, updateFileUseCase: UpdateFileUseCase.Interface);
|
|
18
18
|
run({ input, controller }: TaskDefinition.RunParams<ExtractMetadataInput>): Promise<TaskDefinition.Result<ExtractMetadataInput>>;
|
|
19
19
|
private cleanValues;
|
|
@@ -9,7 +9,7 @@ class FlushCacheOnFileDeleteHandlerImpl {
|
|
|
9
9
|
async handle(event) {
|
|
10
10
|
const { file } = event.payload;
|
|
11
11
|
await this.taskService.trigger({
|
|
12
|
-
definition: "
|
|
12
|
+
definition: "invalidateAssetCache",
|
|
13
13
|
input: {
|
|
14
14
|
caller: "fm-before-delete",
|
|
15
15
|
paths: this.pathsGenerator.generate(file.id)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/FlushCache/FlushCacheOnFileDeleteHandler.js","sources":["../../../src/features/FlushCache/FlushCacheOnFileDeleteHandler.ts"],"sourcesContent":["import { FileAfterDeleteEventHandler } from \"@webiny/api-file-manager/features/file/DeleteFile/events.js\";\nimport { TaskService } from \"@webiny/api-core/features/task/TaskService/index.js\";\nimport { CdnPathsGenerator } from \"~/utils/CdnPathsGenerator.js\";\n\nclass FlushCacheOnFileDeleteHandlerImpl implements FileAfterDeleteEventHandler.Interface {\n private readonly pathsGenerator: CdnPathsGenerator;\n\n constructor(private taskService: TaskService.Interface) {\n this.pathsGenerator = new CdnPathsGenerator();\n }\n\n async handle(event: FileAfterDeleteEventHandler.Event): Promise<void> {\n const { file } = event.payload;\n\n await this.taskService.trigger({\n definition: \"
|
|
1
|
+
{"version":3,"file":"features/FlushCache/FlushCacheOnFileDeleteHandler.js","sources":["../../../src/features/FlushCache/FlushCacheOnFileDeleteHandler.ts"],"sourcesContent":["import { FileAfterDeleteEventHandler } from \"@webiny/api-file-manager/features/file/DeleteFile/events.js\";\nimport { TaskService } from \"@webiny/api-core/features/task/TaskService/index.js\";\nimport { CdnPathsGenerator } from \"~/utils/CdnPathsGenerator.js\";\n\nclass FlushCacheOnFileDeleteHandlerImpl implements FileAfterDeleteEventHandler.Interface {\n private readonly pathsGenerator: CdnPathsGenerator;\n\n constructor(private taskService: TaskService.Interface) {\n this.pathsGenerator = new CdnPathsGenerator();\n }\n\n async handle(event: FileAfterDeleteEventHandler.Event): Promise<void> {\n const { file } = event.payload;\n\n await this.taskService.trigger({\n definition: \"invalidateAssetCache\",\n input: {\n caller: \"fm-before-delete\",\n paths: this.pathsGenerator.generate(file.id)\n }\n });\n }\n}\n\nexport const FlushCacheOnFileDeleteHandler = FileAfterDeleteEventHandler.createImplementation({\n implementation: FlushCacheOnFileDeleteHandlerImpl,\n dependencies: [TaskService]\n});\n"],"names":["FlushCacheOnFileDeleteHandlerImpl","taskService","CdnPathsGenerator","event","file","FlushCacheOnFileDeleteHandler","FileAfterDeleteEventHandler","TaskService"],"mappings":";;;AAIA,MAAMA;IAGF,YAAoBC,WAAkC,CAAE;aAApCA,WAAW,GAAXA;QAChB,IAAI,CAAC,cAAc,GAAG,IAAIC;IAC9B;IAEA,MAAM,OAAOC,KAAwC,EAAiB;QAClE,MAAM,EAAEC,IAAI,EAAE,GAAGD,MAAM,OAAO;QAE9B,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;YAC3B,YAAY;YACZ,OAAO;gBACH,QAAQ;gBACR,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAACC,KAAK,EAAE;YAC/C;QACJ;IACJ;AACJ;AAEO,MAAMC,gCAAgCC,4BAA4B,oBAAoB,CAAC;IAC1F,gBAAgBN;IAChB,cAAc;QAACO;KAAY;AAC/B"}
|
|
@@ -12,7 +12,7 @@ class FlushCacheOnFileUpdateHandlerImpl {
|
|
|
12
12
|
const newAccessControl = file.accessControl;
|
|
13
13
|
if (prevAccessControl?.type === newAccessControl?.type) return;
|
|
14
14
|
await this.taskService.trigger({
|
|
15
|
-
definition: "
|
|
15
|
+
definition: "invalidateAssetCache",
|
|
16
16
|
input: {
|
|
17
17
|
caller: "fm-before-update",
|
|
18
18
|
paths: this.pathsGenerator.generate(file.id)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/FlushCache/FlushCacheOnFileUpdateHandler.js","sources":["../../../src/features/FlushCache/FlushCacheOnFileUpdateHandler.ts"],"sourcesContent":["import { FileBeforeUpdateEventHandler } from \"@webiny/api-file-manager/features/file/UpdateFile/events.js\";\nimport { TaskService } from \"@webiny/api-core/features/task/TaskService/index.js\";\nimport { CdnPathsGenerator } from \"~/utils/CdnPathsGenerator.js\";\n\nclass FlushCacheOnFileUpdateHandlerImpl implements FileBeforeUpdateEventHandler.Interface {\n private readonly pathsGenerator: CdnPathsGenerator;\n\n constructor(private taskService: TaskService.Interface) {\n this.pathsGenerator = new CdnPathsGenerator();\n }\n\n async handle(event: FileBeforeUpdateEventHandler.Event): Promise<void> {\n const { file, original } = event.payload;\n\n const prevAccessControl = original.accessControl;\n const newAccessControl = file.accessControl;\n\n // Only trigger cache flush if access control type has changed\n if (prevAccessControl?.type === newAccessControl?.type) {\n return;\n }\n\n await this.taskService.trigger({\n definition: \"
|
|
1
|
+
{"version":3,"file":"features/FlushCache/FlushCacheOnFileUpdateHandler.js","sources":["../../../src/features/FlushCache/FlushCacheOnFileUpdateHandler.ts"],"sourcesContent":["import { FileBeforeUpdateEventHandler } from \"@webiny/api-file-manager/features/file/UpdateFile/events.js\";\nimport { TaskService } from \"@webiny/api-core/features/task/TaskService/index.js\";\nimport { CdnPathsGenerator } from \"~/utils/CdnPathsGenerator.js\";\n\nclass FlushCacheOnFileUpdateHandlerImpl implements FileBeforeUpdateEventHandler.Interface {\n private readonly pathsGenerator: CdnPathsGenerator;\n\n constructor(private taskService: TaskService.Interface) {\n this.pathsGenerator = new CdnPathsGenerator();\n }\n\n async handle(event: FileBeforeUpdateEventHandler.Event): Promise<void> {\n const { file, original } = event.payload;\n\n const prevAccessControl = original.accessControl;\n const newAccessControl = file.accessControl;\n\n // Only trigger cache flush if access control type has changed\n if (prevAccessControl?.type === newAccessControl?.type) {\n return;\n }\n\n await this.taskService.trigger({\n definition: \"invalidateAssetCache\",\n input: {\n caller: \"fm-before-update\",\n paths: this.pathsGenerator.generate(file.id)\n }\n });\n }\n}\n\nexport const FlushCacheOnFileUpdateHandler = FileBeforeUpdateEventHandler.createImplementation({\n implementation: FlushCacheOnFileUpdateHandlerImpl,\n dependencies: [TaskService]\n});\n"],"names":["FlushCacheOnFileUpdateHandlerImpl","taskService","CdnPathsGenerator","event","file","original","prevAccessControl","newAccessControl","FlushCacheOnFileUpdateHandler","FileBeforeUpdateEventHandler","TaskService"],"mappings":";;;AAIA,MAAMA;IAGF,YAAoBC,WAAkC,CAAE;aAApCA,WAAW,GAAXA;QAChB,IAAI,CAAC,cAAc,GAAG,IAAIC;IAC9B;IAEA,MAAM,OAAOC,KAAyC,EAAiB;QACnE,MAAM,EAAEC,IAAI,EAAEC,QAAQ,EAAE,GAAGF,MAAM,OAAO;QAExC,MAAMG,oBAAoBD,SAAS,aAAa;QAChD,MAAME,mBAAmBH,KAAK,aAAa;QAG3C,IAAIE,mBAAmB,SAASC,kBAAkB,MAC9C;QAGJ,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;YAC3B,YAAY;YACZ,OAAO;gBACH,QAAQ;gBACR,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAACH,KAAK,EAAE;YAC/C;QACJ;IACJ;AACJ;AAEO,MAAMI,gCAAgCC,6BAA6B,oBAAoB,CAAC;IAC3F,gBAAgBT;IAChB,cAAc;QAACU;KAAY;AAC/B"}
|
|
@@ -15,9 +15,9 @@ declare class InvalidateCloudfrontCacheTask implements TaskDefinition.Interface<
|
|
|
15
15
|
description: string;
|
|
16
16
|
maxIterations: number;
|
|
17
17
|
isPrivate: boolean;
|
|
18
|
-
selfCleanup: ("
|
|
18
|
+
selfCleanup: ("onAbort" | "onSuccess")[];
|
|
19
19
|
private continueIfCode;
|
|
20
|
-
run({ input, controller }: TaskDefinition.RunParams<InvalidateCacheInput>): Promise<import("@webiny/api-core/features/task/TaskDefinition/abstractions").
|
|
20
|
+
run({ input, controller }: TaskDefinition.RunParams<InvalidateCacheInput>): Promise<import("@webiny/api-core/features/task/TaskDefinition/abstractions").ITaskResultAborted | import("@webiny/api-core/features/task/TaskDefinition/abstractions").ITaskResultError | TaskDefinition.ResultContinue<InvalidateCacheInput> | TaskDefinition.ResultDone<import("@webiny/api-core/features/task/TaskDefinition/abstractions").ITaskOutput>>;
|
|
21
21
|
private invalidateCache;
|
|
22
22
|
}
|
|
23
23
|
export declare const InvalidateCloudfrontCacheTaskDefinition: typeof InvalidateCloudfrontCacheTask & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServiceDiscovery } from "@webiny/api";
|
|
1
|
+
import { ServiceDiscovery } from "@webiny/api-core/features/serviceDiscovery/index.js";
|
|
2
2
|
import { CloudFront } from "@webiny/aws-sdk/client-cloudfront/index.js";
|
|
3
3
|
import { TaskDefinition } from "@webiny/api-core/features/task/TaskDefinition/index.js";
|
|
4
4
|
import { executeWithRetry } from "@webiny/utils";
|
|
@@ -51,7 +51,7 @@ class InvalidateCloudfrontCacheTask {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
constructor(){
|
|
54
|
-
this.id = "
|
|
54
|
+
this.id = "invalidateAssetCache";
|
|
55
55
|
this.title = "Invalidate CloudFront Cache";
|
|
56
56
|
this.description = "A task to invalidate Cloudfront cache by given paths.";
|
|
57
57
|
this.maxIterations = 100;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/FlushCache/InvalidateCacheTask.js","sources":["../../../src/features/FlushCache/InvalidateCacheTask.ts"],"sourcesContent":["import { ServiceDiscovery } from \"@webiny/api\";\nimport { CloudFront } from \"@webiny/aws-sdk/client-cloudfront/index.js\";\nimport { TaskDefinition } from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { executeWithRetry } from \"@webiny/utils\";\n\nclass ReturnContinue extends Error {}\n\nexport interface InvalidateCacheInput {\n /**\n * Caller of the task (e.g., `fm-before-update`, `fm-after-delete`).\n */\n caller: string;\n /**\n * Cache paths to invalidate.\n */\n paths: string[];\n}\n\nclass InvalidateCloudfrontCacheTask implements TaskDefinition.Interface<InvalidateCacheInput> {\n id = \"
|
|
1
|
+
{"version":3,"file":"features/FlushCache/InvalidateCacheTask.js","sources":["../../../src/features/FlushCache/InvalidateCacheTask.ts"],"sourcesContent":["import { ServiceDiscovery } from \"@webiny/api-core/features/serviceDiscovery/index.js\";\nimport { CloudFront } from \"@webiny/aws-sdk/client-cloudfront/index.js\";\nimport { TaskDefinition } from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { executeWithRetry } from \"@webiny/utils\";\n\nclass ReturnContinue extends Error {}\n\nexport interface InvalidateCacheInput {\n /**\n * Caller of the task (e.g., `fm-before-update`, `fm-after-delete`).\n */\n caller: string;\n /**\n * Cache paths to invalidate.\n */\n paths: string[];\n}\n\nclass InvalidateCloudfrontCacheTask implements TaskDefinition.Interface<InvalidateCacheInput> {\n id = \"invalidateAssetCache\";\n title = \"Invalidate CloudFront Cache\";\n description = \"A task to invalidate Cloudfront cache by given paths.\";\n maxIterations = 100;\n isPrivate = true;\n\n selfCleanup = [\"onSuccess\" as const, \"onAbort\" as const];\n\n private continueIfCode = [\"TooManyInvalidationsInProgress\", \"Throttling\"];\n\n public async run({ input, controller }: TaskDefinition.RunParams<InvalidateCacheInput>) {\n if (controller.runtime.isAborted()) {\n return controller.response.aborted();\n }\n\n const manifest = await ServiceDiscovery.load();\n\n if (!manifest) {\n return controller.response.error({\n message: `Unable to invalidate cache due to a missing service manifest.`,\n code: \"MISSING_SERVICE_MANIFEST\",\n data: {\n manifest: \"api\"\n }\n });\n }\n\n const { distributionId } = manifest.api.cloudfront;\n\n const invalidateCache = () => {\n return this.invalidateCache(input.caller, distributionId as string, input.paths);\n };\n\n try {\n await executeWithRetry(invalidateCache, {\n minTimeout: 2000,\n // instead of forever: true\n retries: 10000,\n onFailedAttempt: ({ error }) => {\n if (this.continueIfCode.includes(error.name)) {\n throw new ReturnContinue();\n }\n\n if (error.message.includes(\"not authorized to perform\")) {\n throw error;\n }\n\n if (controller.runtime.isCloseToTimeout()) {\n throw new ReturnContinue();\n }\n }\n });\n } catch (error) {\n if (error instanceof ReturnContinue) {\n return controller.response.continue(input);\n }\n\n return controller.response.error({\n message: error.message,\n code: \"EXECUTE_WITH_RETRY_FAILED\",\n data: input.paths\n });\n }\n\n return controller.response.done();\n }\n\n private async invalidateCache(\n caller: string,\n distributionId: string,\n paths: string[]\n ): Promise<void> {\n const cloudfront = new CloudFront();\n await cloudfront.createInvalidation({\n DistributionId: distributionId,\n InvalidationBatch: {\n CallerReference: `${new Date().getTime()}-${caller}`,\n Paths: {\n Quantity: paths.length,\n Items: paths\n }\n }\n });\n }\n}\n\nexport const InvalidateCloudfrontCacheTaskDefinition = TaskDefinition.createImplementation({\n implementation: InvalidateCloudfrontCacheTask,\n dependencies: []\n});\n"],"names":["ReturnContinue","Error","InvalidateCloudfrontCacheTask","input","controller","manifest","ServiceDiscovery","distributionId","invalidateCache","executeWithRetry","error","caller","paths","cloudfront","CloudFront","Date","InvalidateCloudfrontCacheTaskDefinition","TaskDefinition"],"mappings":";;;;AAKA,MAAMA,uBAAuBC;AAAO;AAapC,MAAMC;IAWF,MAAa,IAAI,EAAEC,KAAK,EAAEC,UAAU,EAAkD,EAAE;QACpF,IAAIA,WAAW,OAAO,CAAC,SAAS,IAC5B,OAAOA,WAAW,QAAQ,CAAC,OAAO;QAGtC,MAAMC,WAAW,MAAMC,iBAAiB,IAAI;QAE5C,IAAI,CAACD,UACD,OAAOD,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS;YACT,MAAM;YACN,MAAM;gBACF,UAAU;YACd;QACJ;QAGJ,MAAM,EAAEG,cAAc,EAAE,GAAGF,SAAS,GAAG,CAAC,UAAU;QAElD,MAAMG,kBAAkB,IACb,IAAI,CAAC,eAAe,CAACL,MAAM,MAAM,EAAEI,gBAA0BJ,MAAM,KAAK;QAGnF,IAAI;YACA,MAAMM,iBAAiBD,iBAAiB;gBACpC,YAAY;gBAEZ,SAAS;gBACT,iBAAiB,CAAC,EAAEE,KAAK,EAAE;oBACvB,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,CAACA,MAAM,IAAI,GACvC,MAAM,IAAIV;oBAGd,IAAIU,MAAM,OAAO,CAAC,QAAQ,CAAC,8BACvB,MAAMA;oBAGV,IAAIN,WAAW,OAAO,CAAC,gBAAgB,IACnC,MAAM,IAAIJ;gBAElB;YACJ;QACJ,EAAE,OAAOU,OAAO;YACZ,IAAIA,iBAAiBV,gBACjB,OAAOI,WAAW,QAAQ,CAAC,QAAQ,CAACD;YAGxC,OAAOC,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAC7B,SAASM,MAAM,OAAO;gBACtB,MAAM;gBACN,MAAMP,MAAM,KAAK;YACrB;QACJ;QAEA,OAAOC,WAAW,QAAQ,CAAC,IAAI;IACnC;IAEA,MAAc,gBACVO,MAAc,EACdJ,cAAsB,EACtBK,KAAe,EACF;QACb,MAAMC,aAAa,IAAIC;QACvB,MAAMD,WAAW,kBAAkB,CAAC;YAChC,gBAAgBN;YAChB,mBAAmB;gBACf,iBAAiB,GAAG,IAAIQ,OAAO,OAAO,GAAG,CAAC,EAAEJ,QAAQ;gBACpD,OAAO;oBACH,UAAUC,MAAM,MAAM;oBACtB,OAAOA;gBACX;YACJ;QACJ;IACJ;;aAnFA,EAAE,GAAG;aACL,KAAK,GAAG;aACR,WAAW,GAAG;aACd,aAAa,GAAG;aAChB,SAAS,GAAG;aAEZ,WAAW,GAAG;YAAC;YAAsB;SAAmB;aAEhD,cAAc,GAAG;YAAC;YAAkC;SAAa;;AA4E7E;AAEO,MAAMI,0CAA0CC,eAAe,oBAAoB,CAAC;IACvF,gBAAgBf;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -6,7 +6,9 @@ class MetadataWriter {
|
|
|
6
6
|
async write(files) {
|
|
7
7
|
const writers = files.map(async (file)=>{
|
|
8
8
|
const metadata = this.getMetadata(file);
|
|
9
|
-
|
|
9
|
+
const key = `FileManager/File/${file.id}/Metadata`;
|
|
10
|
+
const result = await this.keyValueStore.set(key, metadata);
|
|
11
|
+
if (result.isFail()) console.error(`[FileManagerS3] Failed to write delivery metadata "${key}":`, result.error);
|
|
10
12
|
});
|
|
11
13
|
await Promise.all(writers);
|
|
12
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features/WriteFileMetadata/MetadataWriter.js","sources":["../../../src/features/WriteFileMetadata/MetadataWriter.ts"],"sourcesContent":["import { TenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/index.js\";\nimport type { File } from \"@webiny/api-file-manager/domain/file/types.js\";\nimport { GlobalKeyValueStore } from \"@webiny/api-core/features/keyValueStore/index.js\";\n\nexport class MetadataWriter {\n constructor(\n private tenantContext: TenantContext.Interface,\n private keyValueStore: GlobalKeyValueStore.Interface\n ) {}\n\n async write(files: File[]) {\n /**\n * We need to write each file with retry.\n */\n const writers = files.map(async file => {\n const metadata = this.getMetadata(file);\n
|
|
1
|
+
{"version":3,"file":"features/WriteFileMetadata/MetadataWriter.js","sources":["../../../src/features/WriteFileMetadata/MetadataWriter.ts"],"sourcesContent":["import { TenantContext } from \"@webiny/api-core/features/tenancy/TenantContext/index.js\";\nimport type { File } from \"@webiny/api-file-manager/domain/file/types.js\";\nimport { GlobalKeyValueStore } from \"@webiny/api-core/features/keyValueStore/index.js\";\n\nexport class MetadataWriter {\n constructor(\n private tenantContext: TenantContext.Interface,\n private keyValueStore: GlobalKeyValueStore.Interface\n ) {}\n\n async write(files: File[]) {\n /**\n * We need to write each file with retry.\n */\n const writers = files.map(async file => {\n const metadata = this.getMetadata(file);\n const key = `FileManager/File/${file.id}/Metadata`;\n // Check the result: the KV store returns a failed Result (it does not throw), so an\n // ignored failure here would silently break asset delivery (missing metadata → 404).\n const result = await this.keyValueStore.set(key, metadata);\n if (result.isFail()) {\n console.error(\n `[FileManagerS3] Failed to write delivery metadata \"${key}\":`,\n result.error\n );\n }\n });\n\n await Promise.all(writers);\n }\n\n private getMetadata(file: File) {\n const tenant = this.tenantContext.getTenant();\n return {\n id: file.id,\n bucketKey: `tenants/${tenant.id}/files/${file.key}`,\n tenant: tenant.id,\n size: file.size,\n contentType: file.type\n };\n }\n}\n"],"names":["MetadataWriter","tenantContext","keyValueStore","files","writers","file","metadata","key","result","console","Promise","tenant"],"mappings":"AAIO,MAAMA;IACT,YACYC,aAAsC,EACtCC,aAA4C,CACtD;aAFUD,aAAa,GAAbA;aACAC,aAAa,GAAbA;IACT;IAEH,MAAM,MAAMC,KAAa,EAAE;QAIvB,MAAMC,UAAUD,MAAM,GAAG,CAAC,OAAME;YAC5B,MAAMC,WAAW,IAAI,CAAC,WAAW,CAACD;YAClC,MAAME,MAAM,CAAC,iBAAiB,EAAEF,KAAK,EAAE,CAAC,SAAS,CAAC;YAGlD,MAAMG,SAAS,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAACD,KAAKD;YACjD,IAAIE,OAAO,MAAM,IACbC,QAAQ,KAAK,CACT,CAAC,mDAAmD,EAAEF,IAAI,EAAE,CAAC,EAC7DC,OAAO,KAAK;QAGxB;QAEA,MAAME,QAAQ,GAAG,CAACN;IACtB;IAEQ,YAAYC,IAAU,EAAE;QAC5B,MAAMM,SAAS,IAAI,CAAC,aAAa,CAAC,SAAS;QAC3C,OAAO;YACH,IAAIN,KAAK,EAAE;YACX,WAAW,CAAC,QAAQ,EAAEM,OAAO,EAAE,CAAC,OAAO,EAAEN,KAAK,GAAG,EAAE;YACnD,QAAQM,OAAO,EAAE;YACjB,MAAMN,KAAK,IAAI;YACf,aAAaA,KAAK,IAAI;QAC1B;IACJ;AACJ"}
|