quidproquo-deploy-awscdk 0.0.43 → 0.0.44

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.
@@ -3,7 +3,6 @@ import { Stack } from 'aws-cdk-lib';
3
3
  import { Construct } from 'constructs';
4
4
  export interface QPQPrototypeStackProps extends DeploymentSettings {
5
5
  account: string;
6
- region: string;
7
6
  apiBuildPath: string;
8
7
  webBuildPath: string;
9
8
  lambdaAPIGatewayEventPath?: string;
@@ -24,10 +24,31 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.QPQPrototypeSingleServiceStack = void 0;
27
+ const path = __importStar(require("path"));
27
28
  const aws_cdk_lib_1 = require("aws-cdk-lib");
28
29
  const cdk = __importStar(require("aws-cdk-lib"));
29
30
  const quidproquo_core_1 = require("quidproquo-core");
30
31
  const quidproquo_webserver_1 = require("quidproquo-webserver");
32
+ const grantReadToOwnedResources = (ownedResourceSettings, identity) => {
33
+ ownedResourceSettings.ownedBuckets.forEach((b) => b.bucket.grantReadWrite(identity));
34
+ ownedResourceSettings.ownedSecrets.forEach((os) => os.secret.grantRead(identity));
35
+ ownedResourceSettings.ownedParameters.forEach((os) => os.parameter.grantRead(identity));
36
+ };
37
+ const getParameterName = (parameterKey, service, environment) => `${parameterKey}-${service}-${environment}`;
38
+ const createServiceParameter = (stack, id, service, environment, parameterKey, value) => {
39
+ const realParameterName = getParameterName(parameterKey, service, environment);
40
+ return {
41
+ parameterName: parameterKey,
42
+ realName: realParameterName,
43
+ parameter: new aws_cdk_lib_1.aws_ssm.StringParameter(stack, `${id}-parameter-${parameterKey}`, {
44
+ parameterName: realParameterName,
45
+ description: `${environment}-${service}`,
46
+ stringValue: value,
47
+ // No additional costs ~ 4k max size
48
+ tier: aws_cdk_lib_1.aws_ssm.ParameterTier.STANDARD,
49
+ }),
50
+ };
51
+ };
31
52
  const createApiDomainName = (stack, id, stackProps) => {
32
53
  const apexDomain = quidproquo_webserver_1.qpqWebServerUtils.getFeatureDomainName(stackProps.qpqConfig);
33
54
  const apiDomainName = `api.${apexDomain}`;
@@ -52,7 +73,7 @@ const createApiDomainName = (stack, id, stackProps) => {
52
73
  });
53
74
  return domainName;
54
75
  };
55
- const createWebDistribution = (stack, id, stackProps) => {
76
+ const createWebDistribution = (stack, id, stackProps, ownedResourceSettings) => {
56
77
  const environment = quidproquo_core_1.qpqCoreUtils.getAppFeature(stackProps.qpqConfig);
57
78
  const apexDomain = quidproquo_webserver_1.qpqWebServerUtils.getFeatureDomainName(stackProps.qpqConfig);
58
79
  const serviceName = quidproquo_core_1.qpqCoreUtils.getAppName(stackProps.qpqConfig);
@@ -92,8 +113,52 @@ const createWebDistribution = (stack, id, stackProps) => {
92
113
  sources: [aws_cdk_lib_1.aws_s3_deployment.Source.asset(stackProps.webBuildPath)],
93
114
  destinationBucket: staticWebFilesBucket,
94
115
  });
116
+ const cloudFrontBehaviors = quidproquo_webserver_1.qpqWebServerUtils
117
+ .getAllSeo(stackProps.qpqConfig)
118
+ .map((seo, index) => {
119
+ const edgeFunctionVR = new aws_cdk_lib_1.aws_cloudfront.experimental.EdgeFunction(stack, `${id}-SEO-${index}-${seo.runtime}-VR`, {
120
+ functionName: `SEO-VR-${index}-${seo.runtime}-${environment}-${serviceName}`,
121
+ timeout: cdk.Duration.seconds(5),
122
+ runtime: aws_cdk_lib_1.aws_lambda.Runtime.NODEJS_16_X,
123
+ code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(stackProps.apiBuildPath, 'lambdaEventViewerRequest')),
124
+ handler: 'index.executeEventViewerRequest',
125
+ });
126
+ const edgeFunctionOR = new aws_cdk_lib_1.aws_cloudfront.experimental.EdgeFunction(stack, `${id}-SEO-${index}-${seo.runtime}-OR`, {
127
+ functionName: `SEO-OR-${index}-${seo.runtime}-${environment}-${serviceName}`,
128
+ timeout: cdk.Duration.seconds(30),
129
+ runtime: aws_cdk_lib_1.aws_lambda.Runtime.NODEJS_16_X,
130
+ memorySize: 1024,
131
+ code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(stackProps.apiBuildPath, 'lambdaEventOriginRequest')),
132
+ handler: 'index.executeEventOriginRequest',
133
+ });
134
+ // We don't need access to anything in the VR
135
+ grantReadToOwnedResources(ownedResourceSettings, edgeFunctionOR);
136
+ const wildcardPath = seo.path.replaceAll(/{(.+?)}/g, '*');
137
+ return {
138
+ pathPattern: wildcardPath,
139
+ // Update this to 24 hours
140
+ maxTtl: cdk.Duration.seconds(0),
141
+ minTtl: cdk.Duration.seconds(0),
142
+ defaultTtl: cdk.Duration.seconds(0),
143
+ lambdaFunctionAssociations: [
144
+ {
145
+ includeBody: true,
146
+ eventType: aws_cdk_lib_1.aws_cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
147
+ lambdaFunction: edgeFunctionOR.currentVersion,
148
+ },
149
+ {
150
+ includeBody: false,
151
+ eventType: aws_cdk_lib_1.aws_cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
152
+ lambdaFunction: edgeFunctionVR.currentVersion,
153
+ },
154
+ ],
155
+ };
156
+ });
95
157
  // Create a cloud front distribution
96
158
  // TODO: use aws_cloudfront.Distribution
159
+ // TODO: Somehow expose query strings: Note ~
160
+ // (Lambda@Edge only) To access the query string in an origin request or origin response function,
161
+ // your cache policy or origin request policy must be set to All for Query strings.
97
162
  const distribution = new aws_cdk_lib_1.aws_cloudfront.CloudFrontWebDistribution(stack, 'cf-distribution', {
98
163
  originConfigs: [
99
164
  {
@@ -102,16 +167,7 @@ const createWebDistribution = (stack, id, stackProps) => {
102
167
  originAccessIdentity: websiteOAI,
103
168
  },
104
169
  behaviors: [
105
- ...[
106
- '/remoteEntry.js',
107
- '/index.js',
108
- '/index.html', // Add this if we need it
109
- ].map((pp) => ({
110
- pathPattern: pp,
111
- maxTtl: cdk.Duration.seconds(0),
112
- minTtl: cdk.Duration.seconds(0),
113
- defaultTtl: cdk.Duration.seconds(0),
114
- })),
170
+ ...cloudFrontBehaviors,
115
171
  {
116
172
  isDefaultBehavior: true,
117
173
  },
@@ -140,7 +196,7 @@ class QPQPrototypeSingleServiceStack extends aws_cdk_lib_1.Stack {
140
196
  super(scope, id, {
141
197
  env: {
142
198
  account: props.account,
143
- region: props.region,
199
+ region: quidproquo_webserver_1.qpqWebServerUtils.getDeployRegion(props.qpqConfig),
144
200
  },
145
201
  });
146
202
  const settings = {
@@ -148,7 +204,6 @@ class QPQPrototypeSingleServiceStack extends aws_cdk_lib_1.Stack {
148
204
  service: quidproquo_core_1.qpqCoreUtils.getAppName(props.qpqConfig),
149
205
  };
150
206
  const domainName = createApiDomainName(this, id, props);
151
- createWebDistribution(this, `${id}-web-dist`, props);
152
207
  const resourceName = (name) => {
153
208
  return `${settings.service}-${settings.environment}-${name}`;
154
209
  };
@@ -164,21 +219,10 @@ class QPQPrototypeSingleServiceStack extends aws_cdk_lib_1.Stack {
164
219
  }),
165
220
  };
166
221
  });
167
- const ownedParameters = quidproquo_core_1.qpqCoreUtils.getOwnedParameters(props.qpqConfig).map((parameter) => {
168
- const realParameterName = `${parameter.key}-${settings.service}-${settings.environment}`;
169
- return {
170
- parameterName: parameter.key,
171
- realName: realParameterName,
172
- parameter: new aws_cdk_lib_1.aws_ssm.StringParameter(this, `${id}-parameter-${parameter.key}`, {
173
- parameterName: realParameterName,
174
- description: `${settings.environment}-${settings.service}`,
175
- stringValue: parameter.value,
176
- // No additional costs ~ 4k max size
177
- tier: aws_cdk_lib_1.aws_ssm.ParameterTier.STANDARD,
178
- }),
179
- };
180
- });
181
- const buckets = quidproquo_core_1.qpqCoreUtils.getStorageDriveNames(props.qpqConfig).map((driveName) => {
222
+ const ownedParameters = quidproquo_core_1.qpqCoreUtils
223
+ .getOwnedParameters(props.qpqConfig)
224
+ .map((parameter) => createServiceParameter(this, id, settings.service, settings.environment, parameter.key, parameter.value));
225
+ const ownedBuckets = quidproquo_core_1.qpqCoreUtils.getStorageDriveNames(props.qpqConfig).map((driveName) => {
182
226
  const bucketName = resourceName(driveName);
183
227
  const bucket = new aws_cdk_lib_1.aws_s3.Bucket(this, bucketName, {
184
228
  bucketName: resourceName(driveName),
@@ -199,25 +243,35 @@ class QPQPrototypeSingleServiceStack extends aws_cdk_lib_1.Stack {
199
243
  const runtimeConfigLambdaConfig = {
200
244
  qpqConfig: props.qpqConfig,
201
245
  secretNameMap: ownedSecrets.reduce((acc, os) => (Object.assign(Object.assign({}, acc), { [os.secretName]: os.realName })), {}),
202
- parameterNameMap: ownedParameters.reduce((acc, os) => (Object.assign(Object.assign({}, acc), { [os.parameterName]: os.realName })), {}),
203
- resourceNameMap: buckets.reduce((acc, b) => (Object.assign(Object.assign({}, acc), { [b.driveName]: b.bucketName })), {}),
246
+ parameterNameMap: Object.assign(Object.assign({}, ownedParameters.reduce((acc, os) => (Object.assign(Object.assign({}, acc), { [os.parameterName]: os.realName })), {})), { ['qpqRuntimeConfig']: getParameterName('qpqRuntimeConfig', settings.service, settings.environment) }),
247
+ resourceNameMap: ownedBuckets.reduce((acc, b) => (Object.assign(Object.assign({}, acc), { [b.driveName]: b.bucketName })), {}),
248
+ };
249
+ // Build the param that stores the config
250
+ // Note: The mapping was done manually previously inside the value
251
+ ownedParameters.push(createServiceParameter(this, id, settings.service, settings.environment, 'qpqRuntimeConfig', JSON.stringify(runtimeConfigLambdaConfig)));
252
+ const ownedResourceSettings = {
253
+ ownedBuckets,
254
+ ownedParameters,
255
+ ownedSecrets,
204
256
  };
257
+ ////////////////////////////////////////////////////////////////////////////////////
258
+ // Finished building resources, now we can build the lambdas
259
+ ////////////////////////////////////////////////////////////////////////////////////
260
+ createWebDistribution(this, `${id}-web-dist`, props, ownedResourceSettings);
205
261
  const lambdaHandler = new aws_cdk_lib_1.aws_lambda.Function(this, `${settings.environment}-${settings.service}-lambda-rest`, {
206
262
  functionName: `lambda-rest-${settings.service}-${settings.environment}`,
207
263
  timeout: cdk.Duration.seconds(25),
208
264
  runtime: aws_cdk_lib_1.aws_lambda.Runtime.NODEJS_16_X,
209
265
  memorySize: 1024,
210
- code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(props.apiBuildPath),
211
- handler: 'lambdaAPIGatewayEvent.executeAPIGatewayEvent',
266
+ code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(props.apiBuildPath, 'lambdaAPIGatewayEvent')),
267
+ handler: 'index.executeAPIGatewayEvent',
212
268
  environment: {
213
269
  TABLES: JSON.stringify([]),
214
270
  lambdaRuntimeConfig: JSON.stringify(runtimeConfigLambdaConfig),
215
271
  },
216
272
  });
217
273
  // Grant access to the lambda
218
- buckets.forEach((b) => b.bucket.grantReadWrite(lambdaHandler));
219
- ownedSecrets.forEach((os) => os.secret.grantRead(lambdaHandler));
220
- ownedParameters.forEach((os) => os.parameter.grantRead(lambdaHandler));
274
+ grantReadToOwnedResources(ownedResourceSettings, lambdaHandler);
221
275
  // Create a rest api
222
276
  const api = new aws_cdk_lib_1.aws_apigateway.LambdaRestApi(this, `${settings.environment}-${settings.service}-http-api`, {
223
277
  restApiName: `${settings.environment}-${settings.service}-http-api`,
@@ -241,8 +295,8 @@ class QPQPrototypeSingleServiceStack extends aws_cdk_lib_1.Stack {
241
295
  timeout: cdk.Duration.minutes(15),
242
296
  runtime: aws_cdk_lib_1.aws_lambda.Runtime.NODEJS_16_X,
243
297
  memorySize: 1024,
244
- code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(props.apiBuildPath),
245
- handler: 'lambdaEventBridgeEvent.executeEventBridgeEvent',
298
+ code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(props.apiBuildPath, 'lambdaEventBridgeEvent')),
299
+ handler: 'index.executeEventBridgeEvent',
246
300
  environment: {
247
301
  TABLES: JSON.stringify([]),
248
302
  lambdaRuntimeConfig: JSON.stringify(Object.assign(Object.assign({}, runtimeConfigLambdaConfig), { lambdaRuntimeConfig: {
@@ -251,9 +305,7 @@ class QPQPrototypeSingleServiceStack extends aws_cdk_lib_1.Stack {
251
305
  } })),
252
306
  },
253
307
  });
254
- buckets.forEach((b) => b.bucket.grantReadWrite(schedulerFunction));
255
- ownedSecrets.forEach((os) => os.secret.grantRead(schedulerFunction));
256
- ownedParameters.forEach((os) => os.parameter.grantRead(schedulerFunction));
308
+ grantReadToOwnedResources(ownedResourceSettings, schedulerFunction);
257
309
  // EventBridge rule which runs every five minutes
258
310
  const cronRule = new aws_cdk_lib_1.aws_events.Rule(this, `${id}-se-cronrule-${index}`, {
259
311
  schedule: aws_cdk_lib_1.aws_events.Schedule.expression(`cron(${se.cronExpression})`),
@@ -0,0 +1,3 @@
1
+ import { ActionProcessorList } from 'quidproquo-core';
2
+ import { QPQAWSLambdaConfig } from 'quidproquo-actionprocessor-awslambda';
3
+ export type ActionProcessorListResolver = (lambdaRuntimeConfig: QPQAWSLambdaConfig) => ActionProcessorList;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,51 @@
1
+ interface CloudFrontConfig {
2
+ distributionDomainName: string;
3
+ distributionId: string;
4
+ eventType: string;
5
+ requestId: string;
6
+ }
7
+ interface Body {
8
+ action: string;
9
+ data: string;
10
+ encoding: string;
11
+ inputTruncated: boolean;
12
+ }
13
+ interface KeyValueHeader {
14
+ key: string;
15
+ value: string;
16
+ }
17
+ interface Headers {
18
+ [key: string]: KeyValueHeader[];
19
+ }
20
+ interface CustomHeaders {
21
+ }
22
+ interface S3 {
23
+ authMethod: string;
24
+ customHeaders: CustomHeaders;
25
+ domainName: string;
26
+ path: string;
27
+ region: string;
28
+ }
29
+ interface Origin {
30
+ s3: S3;
31
+ }
32
+ interface CloudFrontOriginRequest {
33
+ body?: Body;
34
+ clientIp: string;
35
+ headers: Headers;
36
+ method: string;
37
+ origin: Origin;
38
+ querystring: string;
39
+ uri: string;
40
+ }
41
+ interface CloudFrontOriginConfigRequest {
42
+ config: CloudFrontConfig;
43
+ request: CloudFrontOriginRequest;
44
+ }
45
+ interface CloudFrontRecord {
46
+ cf: CloudFrontOriginConfigRequest;
47
+ }
48
+ export interface CloudFrontOriginRequestEvent {
49
+ Records: CloudFrontRecord[];
50
+ }
51
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,6 @@
1
1
  import { DynamicModuleLoader } from 'quidproquo-actionprocessor-awslambda';
2
2
  import { Context } from 'aws-lambda';
3
- import { ActionProcessorListResolver } from './lambdaConfig';
3
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
4
4
  export declare const getAPIGatewayEventExecutor: (dynamicModuleLoader: DynamicModuleLoader, getCustomActionProcessors?: ActionProcessorListResolver) => (event: import("aws-lambda").APIGatewayProxyEvent, context: Context) => Promise<{
5
5
  statusCode: any;
6
6
  body: string;
@@ -63,7 +63,6 @@ const getAPIGatewayEventExecutor = (dynamicModuleLoader, getCustomActionProcesso
63
63
  headers: result.result.headers,
64
64
  };
65
65
  }
66
- console.log(JSON.stringify(result));
67
66
  const code = ErrorTypeHttpResponseMap[result.error.errorType];
68
67
  return {
69
68
  statusCode: code || 500,
@@ -0,0 +1,4 @@
1
+ import { ActionProcessorList } from 'quidproquo-core';
2
+ import { QPQAWSLambdaConfig } from 'quidproquo-actionprocessor-awslambda';
3
+ export declare const lambdaRuntimeConfig: QPQAWSLambdaConfig;
4
+ export type ActionProcessorListResolver = (lambdaRuntimeConfig: QPQAWSLambdaConfig) => ActionProcessorList;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lambdaRuntimeConfig = void 0;
4
+ exports.lambdaRuntimeConfig = JSON.parse(process.env.lambdaRuntimeConfig || '{}');
@@ -1,4 +1,2 @@
1
- import { ActionProcessorList } from 'quidproquo-core';
2
1
  import { QPQAWSLambdaConfig } from 'quidproquo-actionprocessor-awslambda';
3
2
  export declare const lambdaRuntimeConfig: QPQAWSLambdaConfig;
4
- export type ActionProcessorListResolver = (lambdaRuntimeConfig: QPQAWSLambdaConfig) => ActionProcessorList;
@@ -1,5 +1,5 @@
1
1
  import { DynamicModuleLoader } from 'quidproquo-actionprocessor-awslambda';
2
2
  import { EventBridgeEvent, Context } from 'aws-lambda';
3
- import { ActionProcessorListResolver } from './lambdaConfig';
3
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
4
4
  export declare const getEventBridgeEventExecutor: (dynamicModuleLoader: DynamicModuleLoader, getCustomActionProcessors?: ActionProcessorListResolver) => (event: EventBridgeEvent<string, void>, context: Context) => Promise<void>;
5
5
  export declare const executeEventBridgeEvent: (event: EventBridgeEvent<string, void>, context: Context) => Promise<void>;
@@ -0,0 +1,5 @@
1
+ import { DynamicModuleLoader } from 'quidproquo-actionprocessor-awslambda';
2
+ import { CloudFrontRequestEvent, Context } from 'aws-lambda';
3
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
4
+ export declare const getOriginRequestEventExecutor: (dynamicModuleLoader: DynamicModuleLoader, getCustomActionProcessors?: ActionProcessorListResolver) => (event: CloudFrontRequestEvent, context: Context) => Promise<any>;
5
+ export declare const executeEventOriginRequest: (event: CloudFrontRequestEvent, context: Context) => Promise<any>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.executeEventOriginRequest = exports.getOriginRequestEventExecutor = void 0;
16
+ const quidproquo_actionprocessor_node_1 = require("quidproquo-actionprocessor-node");
17
+ const quidproquo_actionprocessor_awslambda_1 = require("quidproquo-actionprocessor-awslambda");
18
+ const quidproquo_core_1 = require("quidproquo-core");
19
+ // @ts-ignore - Special webpack loader
20
+ const qpq_dynamic_loader_1 = __importDefault(require("qpq-dynamic-loader!"));
21
+ // @ts-ignore - Special webpack loader
22
+ // import qpqConfig from 'qpq-config-loader!';
23
+ // TODO: Make this a util or something based on server time or something..
24
+ const getDateNow = () => new Date().toISOString();
25
+ const ErrorTypeHttpResponseMap = {
26
+ [quidproquo_core_1.ErrorTypeEnum.NoContent]: 204,
27
+ [quidproquo_core_1.ErrorTypeEnum.BadRequest]: 400,
28
+ [quidproquo_core_1.ErrorTypeEnum.Unauthorized]: 401,
29
+ [quidproquo_core_1.ErrorTypeEnum.PaymentRequired]: 402,
30
+ [quidproquo_core_1.ErrorTypeEnum.Forbidden]: 403,
31
+ [quidproquo_core_1.ErrorTypeEnum.NotFound]: 404,
32
+ [quidproquo_core_1.ErrorTypeEnum.TimeOut]: 408,
33
+ [quidproquo_core_1.ErrorTypeEnum.UnsupportedMediaType]: 415,
34
+ [quidproquo_core_1.ErrorTypeEnum.Invalid]: 422,
35
+ [quidproquo_core_1.ErrorTypeEnum.OutOfResources]: 500,
36
+ [quidproquo_core_1.ErrorTypeEnum.GenericError]: 500,
37
+ [quidproquo_core_1.ErrorTypeEnum.NotImplemented]: 501,
38
+ };
39
+ const getOriginRequestEventExecutor = (dynamicModuleLoader, getCustomActionProcessors = () => ({})) => {
40
+ return (event, context) => __awaiter(void 0, void 0, void 0, function* () {
41
+ console.log('AAAAAAAAAAAAAAAAA');
42
+ // TODO: Make this error safe
43
+ const param = yield (0, quidproquo_actionprocessor_awslambda_1.getParameter)('qpqRuntimeConfig-theconspiracytest-development');
44
+ const lambdaRuntimeConfig = JSON.parse(param);
45
+ // //////////////////////////
46
+ console.log('BBBBBBBBBB');
47
+ // Build a processor for the session and stuff
48
+ // Remove the non route ones ~ let the story execute action add them
49
+ const storyActionProcessor = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, quidproquo_actionprocessor_node_1.coreActionProcessor), quidproquo_actionprocessor_node_1.webserverActionProcessor), (0, quidproquo_actionprocessor_awslambda_1.getCloudFrontOriginRequestEventActionProcessor)(lambdaRuntimeConfig.qpqConfig)), (0, quidproquo_actionprocessor_awslambda_1.getConfigGetSecretActionProcessor)(lambdaRuntimeConfig)), (0, quidproquo_actionprocessor_awslambda_1.getConfigGetParameterActionProcessor)(lambdaRuntimeConfig)), (0, quidproquo_actionprocessor_awslambda_1.getConfigGetParametersActionProcessor)(lambdaRuntimeConfig)), (0, quidproquo_actionprocessor_awslambda_1.getSystemActionProcessor)(dynamicModuleLoader)), (0, quidproquo_actionprocessor_awslambda_1.getFileActionProcessor)(lambdaRuntimeConfig)), getCustomActionProcessors(lambdaRuntimeConfig));
50
+ console.log('CCCCCCCCCCCCCCC');
51
+ const logger = (result) => __awaiter(void 0, void 0, void 0, function* () { });
52
+ // Run the callback
53
+ const resolveStory = (0, quidproquo_core_1.createRuntime)({}, storyActionProcessor, getDateNow, logger, quidproquo_actionprocessor_awslambda_1.awsLambdaUtils.randomGuid);
54
+ console.log('DDDDDDDDDDDD');
55
+ const result = yield resolveStory(quidproquo_core_1.askProcessEvent, [event, context]);
56
+ console.log('EEEEEEEEEEEEEE');
57
+ console.log(JSON.stringify(result));
58
+ // Run the callback
59
+ if (!result.error) {
60
+ return result.result;
61
+ }
62
+ console.log('FFFFFFFFFFFFFFFFFFF');
63
+ const code = ErrorTypeHttpResponseMap[result.error.errorType];
64
+ return {
65
+ statusCode: code || 500,
66
+ statusDescription: result.error.errorType,
67
+ body: JSON.stringify(result.error),
68
+ headers: {},
69
+ };
70
+ });
71
+ };
72
+ exports.getOriginRequestEventExecutor = getOriginRequestEventExecutor;
73
+ // Default executor
74
+ exports.executeEventOriginRequest = (0, exports.getOriginRequestEventExecutor)(qpq_dynamic_loader_1.default);
@@ -0,0 +1,15 @@
1
+ import { DynamicModuleLoader } from 'quidproquo-actionprocessor-awslambda';
2
+ import { CloudFrontRequestEvent, Context } from 'aws-lambda';
3
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
4
+ export declare const getOriginRequestEventExecutor: (dynamicModuleLoader: DynamicModuleLoader, getCustomActionProcessors?: ActionProcessorListResolver) => (event: CloudFrontRequestEvent, context: Context) => Promise<import("aws-lambda").CloudFrontRequest | {
5
+ status: string;
6
+ statusDescription: string;
7
+ body: any;
8
+ headers: {};
9
+ }>;
10
+ export declare const executeEventOriginRequest: (event: CloudFrontRequestEvent, context: Context) => Promise<import("aws-lambda").CloudFrontRequest | {
11
+ status: string;
12
+ statusDescription: string;
13
+ body: any;
14
+ headers: {};
15
+ }>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.executeEventOriginRequest = exports.getOriginRequestEventExecutor = void 0;
16
+ const quidproquo_actionprocessor_node_1 = require("quidproquo-actionprocessor-node");
17
+ const quidproquo_actionprocessor_awslambda_1 = require("quidproquo-actionprocessor-awslambda");
18
+ const quidproquo_webserver_1 = require("quidproquo-webserver");
19
+ const quidproquo_core_1 = require("quidproquo-core");
20
+ // @ts-ignore - Special webpack loader
21
+ const qpq_dynamic_loader_1 = __importDefault(require("qpq-dynamic-loader!"));
22
+ // @ts-ignore - Special webpack loader
23
+ const qpq_config_loader_1 = __importDefault(require("qpq-config-loader!"));
24
+ // TODO: Make this a util or something based on server time or something..
25
+ const getDateNow = () => new Date().toISOString();
26
+ const getOriginRequestEventExecutor = (dynamicModuleLoader, getCustomActionProcessors = () => ({})) => {
27
+ return (event, context) => __awaiter(void 0, void 0, void 0, function* () {
28
+ // Don't run the lambda if we are not a bot
29
+ if (event.Records[0].cf.request.headers['x-qpq-is-bot'][0].value !== 'true') {
30
+ return event.Records[0].cf.request;
31
+ }
32
+ // TODO: Make this error safe
33
+ const service = quidproquo_core_1.qpqCoreUtils.getAppName(qpq_config_loader_1.default);
34
+ const environment = quidproquo_core_1.qpqCoreUtils.getAppFeature(qpq_config_loader_1.default);
35
+ const param = yield (0, quidproquo_actionprocessor_awslambda_1.getParameter)(`qpqRuntimeConfig-${service}-${environment}`, quidproquo_webserver_1.qpqWebServerUtils.getDeployRegion(qpq_config_loader_1.default));
36
+ const lambdaRuntimeConfig = JSON.parse(param);
37
+ // //////////////////////////
38
+ // Build a processor for the session and stuff
39
+ // Remove the non route ones ~ let the story execute action add them
40
+ const storyActionProcessor = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, quidproquo_actionprocessor_node_1.coreActionProcessor), quidproquo_actionprocessor_node_1.webserverActionProcessor), (0, quidproquo_actionprocessor_awslambda_1.getCloudFrontOriginRequestEventActionProcessor)(lambdaRuntimeConfig.qpqConfig)), (0, quidproquo_actionprocessor_awslambda_1.getConfigGetSecretActionProcessor)(lambdaRuntimeConfig)), (0, quidproquo_actionprocessor_awslambda_1.getConfigGetParameterActionProcessor)(lambdaRuntimeConfig)), (0, quidproquo_actionprocessor_awslambda_1.getConfigGetParametersActionProcessor)(lambdaRuntimeConfig)), (0, quidproquo_actionprocessor_awslambda_1.getSystemActionProcessor)(dynamicModuleLoader)), (0, quidproquo_actionprocessor_awslambda_1.getFileActionProcessor)(lambdaRuntimeConfig)), getCustomActionProcessors(lambdaRuntimeConfig));
41
+ const logger = (result) => __awaiter(void 0, void 0, void 0, function* () { });
42
+ // Run the callback
43
+ const resolveStory = (0, quidproquo_core_1.createRuntime)({}, storyActionProcessor, getDateNow, logger, quidproquo_actionprocessor_awslambda_1.awsLambdaUtils.randomGuid);
44
+ const result = yield resolveStory(quidproquo_core_1.askProcessEvent, [event, context]);
45
+ // Run the callback
46
+ if (!result.error) {
47
+ // TODO: Get typing to work here.
48
+ if (result.result.fallbackToCDN) {
49
+ // to forward the result to the origin, just return the request
50
+ // it has the origin details.
51
+ return event.Records[0].cf.request;
52
+ }
53
+ const headers = result.result.headers || {};
54
+ const responseHeaders = Object.keys(headers).reduce((acc, header) => (Object.assign(Object.assign({}, acc), { [header]: [{ value: headers[header] }] })), {});
55
+ return {
56
+ status: `${result.result.status}`,
57
+ statusDescription: 'OK',
58
+ body: result.result.body,
59
+ headers: responseHeaders,
60
+ };
61
+ }
62
+ // CloudFront seems to throw an error when you send back a 404 or something, so we will just hit the normal origin
63
+ return event.Records[0].cf.request;
64
+ // const code = ErrorTypeHttpResponseMap[result.error.errorType];
65
+ // return {
66
+ // statusCode: code || 500,
67
+ // statusDescription: result.error.errorType,
68
+ // body: JSON.stringify(result.error),
69
+ // headers: {},
70
+ // };
71
+ });
72
+ };
73
+ exports.getOriginRequestEventExecutor = getOriginRequestEventExecutor;
74
+ // Default executor
75
+ exports.executeEventOriginRequest = (0, exports.getOriginRequestEventExecutor)(qpq_dynamic_loader_1.default);
@@ -0,0 +1,39 @@
1
+ import { CloudFrontRequestEvent, Context } from 'aws-lambda';
2
+ export declare const getViewerRequestEventExecutor: () => (event: CloudFrontRequestEvent, context: Context) => Promise<{
3
+ headers: {
4
+ "x-qpq-is-bot": {
5
+ key: string;
6
+ value: string;
7
+ }[];
8
+ };
9
+ body?: {
10
+ action: "read-only" | "replace";
11
+ data: string;
12
+ encoding: "base64" | "text";
13
+ readonly inputTruncated: boolean;
14
+ } | undefined;
15
+ clientIp: string;
16
+ method: string;
17
+ uri: string;
18
+ querystring: string;
19
+ origin?: import("aws-lambda").CloudFrontOrigin | undefined;
20
+ }>;
21
+ export declare const executeEventViewerRequest: (event: CloudFrontRequestEvent, context: Context) => Promise<{
22
+ headers: {
23
+ "x-qpq-is-bot": {
24
+ key: string;
25
+ value: string;
26
+ }[];
27
+ };
28
+ body?: {
29
+ action: "read-only" | "replace";
30
+ data: string;
31
+ encoding: "base64" | "text";
32
+ readonly inputTruncated: boolean;
33
+ } | undefined;
34
+ clientIp: string;
35
+ method: string;
36
+ uri: string;
37
+ querystring: string;
38
+ origin?: import("aws-lambda").CloudFrontOrigin | undefined;
39
+ }>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.executeEventViewerRequest = exports.getViewerRequestEventExecutor = void 0;
13
+ const getViewerRequestEventExecutor = () => {
14
+ return (event, context) => __awaiter(void 0, void 0, void 0, function* () {
15
+ var _a;
16
+ const request = event.Records[0].cf.request;
17
+ const headers = request.headers;
18
+ const customUserAgentHeaderName = 'x-qpq-is-bot';
19
+ const userAgent = ((_a = headers['user-agent'][0]) === null || _a === void 0 ? void 0 : _a.value) || 'unknown';
20
+ let isBot = userAgent.match(/bot|crawl|spider|slurp|facebot|facebookexternalhit/i);
21
+ return Object.assign(Object.assign({}, request), { headers: Object.assign(Object.assign({}, request.headers), { [customUserAgentHeaderName]: [
22
+ {
23
+ key: customUserAgentHeaderName,
24
+ value: isBot ? 'true' : 'false',
25
+ },
26
+ ] }) });
27
+ });
28
+ };
29
+ exports.getViewerRequestEventExecutor = getViewerRequestEventExecutor;
30
+ // Default executor
31
+ exports.executeEventViewerRequest = (0, exports.getViewerRequestEventExecutor)();
@@ -0,0 +1,51 @@
1
+ interface CloudFrontConfig {
2
+ distributionDomainName: string;
3
+ distributionId: string;
4
+ eventType: string;
5
+ requestId: string;
6
+ }
7
+ interface Body {
8
+ action: string;
9
+ data: string;
10
+ encoding: string;
11
+ inputTruncated: boolean;
12
+ }
13
+ interface KeyValueHeader {
14
+ key: string;
15
+ value: string;
16
+ }
17
+ interface Headers {
18
+ [key: string]: KeyValueHeader[];
19
+ }
20
+ interface CustomHeaders {
21
+ }
22
+ interface S3 {
23
+ authMethod: string;
24
+ customHeaders: CustomHeaders;
25
+ domainName: string;
26
+ path: string;
27
+ region: string;
28
+ }
29
+ interface Origin {
30
+ s3: S3;
31
+ }
32
+ interface CloudFrontOriginRequest {
33
+ body?: Body;
34
+ clientIp: string;
35
+ headers: Headers;
36
+ method: string;
37
+ origin: Origin;
38
+ querystring: string;
39
+ uri: string;
40
+ }
41
+ interface CloudFrontOriginConfigRequest {
42
+ config: CloudFrontConfig;
43
+ request: CloudFrontOriginRequest;
44
+ }
45
+ interface CloudFrontRecord {
46
+ cf: CloudFrontOriginConfigRequest;
47
+ }
48
+ export interface CloudFrontOriginRequestEvent {
49
+ Records: CloudFrontRecord[];
50
+ }
51
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quidproquo-deploy-awscdk",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.js",
@@ -12,7 +12,7 @@
12
12
  "test": "echo \"Error: no test specified\" && exit 1",
13
13
  "clean": "npx rimraf lib",
14
14
  "build": "",
15
- "watch": "npm run clean && tsc -w"
15
+ "watch": "tsc -w"
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
@@ -28,7 +28,6 @@ import { qpqWebServerUtils } from 'quidproquo-webserver';
28
28
 
29
29
  export interface QPQPrototypeStackProps extends DeploymentSettings {
30
30
  account: string;
31
- region: string;
32
31
 
33
32
  apiBuildPath: string;
34
33
  webBuildPath: string;
@@ -37,6 +36,65 @@ export interface QPQPrototypeStackProps extends DeploymentSettings {
37
36
  lambdaEventBridgeEventPath?: string;
38
37
  }
39
38
 
39
+ interface OwnedBucket {
40
+ driveName: string;
41
+ bucketName: string;
42
+ bucket: aws_s3.Bucket;
43
+ }
44
+
45
+ interface OwnedSecret {
46
+ secretName: string;
47
+ realName: string;
48
+ secret: aws_secretsmanager.Secret;
49
+ }
50
+
51
+ interface OwnedParameter {
52
+ parameterName: string;
53
+ realName: string;
54
+ parameter: aws_ssm.StringParameter;
55
+ }
56
+
57
+ interface OwnedResourceSettings {
58
+ ownedBuckets: OwnedBucket[];
59
+ ownedSecrets: OwnedSecret[];
60
+ ownedParameters: OwnedParameter[];
61
+ }
62
+
63
+ const grantReadToOwnedResources = (
64
+ ownedResourceSettings: OwnedResourceSettings,
65
+ identity: cdk.aws_iam.IGrantable,
66
+ ) => {
67
+ ownedResourceSettings.ownedBuckets.forEach((b) => b.bucket.grantReadWrite(identity));
68
+ ownedResourceSettings.ownedSecrets.forEach((os) => os.secret.grantRead(identity));
69
+ ownedResourceSettings.ownedParameters.forEach((os) => os.parameter.grantRead(identity));
70
+ };
71
+
72
+ const getParameterName = (parameterKey: string, service: string, environment: string) =>
73
+ `${parameterKey}-${service}-${environment}`;
74
+
75
+ const createServiceParameter = (
76
+ stack: cdk.Stack,
77
+ id: string,
78
+ service: string,
79
+ environment: string,
80
+ parameterKey: string,
81
+ value: string,
82
+ ) => {
83
+ const realParameterName = getParameterName(parameterKey, service, environment);
84
+ return {
85
+ parameterName: parameterKey,
86
+ realName: realParameterName,
87
+ parameter: new aws_ssm.StringParameter(stack, `${id}-parameter-${parameterKey}`, {
88
+ parameterName: realParameterName,
89
+ description: `${environment}-${service}`,
90
+ stringValue: value,
91
+
92
+ // No additional costs ~ 4k max size
93
+ tier: aws_ssm.ParameterTier.STANDARD,
94
+ }),
95
+ };
96
+ };
97
+
40
98
  const createApiDomainName = (stack: cdk.Stack, id: string, stackProps: QPQPrototypeStackProps) => {
41
99
  const apexDomain = qpqWebServerUtils.getFeatureDomainName(stackProps.qpqConfig);
42
100
 
@@ -74,6 +132,7 @@ const createWebDistribution = (
74
132
  stack: cdk.Stack,
75
133
  id: string,
76
134
  stackProps: QPQPrototypeStackProps,
135
+ ownedResourceSettings: OwnedResourceSettings,
77
136
  ) => {
78
137
  const environment = qpqCoreUtils.getAppFeature(stackProps.qpqConfig);
79
138
  const apexDomain = qpqWebServerUtils.getFeatureDomainName(stackProps.qpqConfig);
@@ -127,8 +186,74 @@ const createWebDistribution = (
127
186
  destinationBucket: staticWebFilesBucket,
128
187
  });
129
188
 
189
+ const cloudFrontBehaviors = qpqWebServerUtils
190
+ .getAllSeo(stackProps.qpqConfig)
191
+ .map((seo, index) => {
192
+ const edgeFunctionVR = new aws_cloudfront.experimental.EdgeFunction(
193
+ stack,
194
+ `${id}-SEO-${index}-${seo.runtime}-VR`,
195
+ {
196
+ functionName: `SEO-VR-${index}-${seo.runtime}-${environment}-${serviceName}`,
197
+ timeout: cdk.Duration.seconds(5),
198
+ runtime: aws_lambda.Runtime.NODEJS_16_X,
199
+
200
+ code: aws_lambda.Code.fromAsset(
201
+ path.join(stackProps.apiBuildPath, 'lambdaEventViewerRequest'),
202
+ ),
203
+ handler: 'index.executeEventViewerRequest',
204
+ },
205
+ );
206
+
207
+ const edgeFunctionOR = new aws_cloudfront.experimental.EdgeFunction(
208
+ stack,
209
+ `${id}-SEO-${index}-${seo.runtime}-OR`,
210
+ {
211
+ functionName: `SEO-OR-${index}-${seo.runtime}-${environment}-${serviceName}`,
212
+ timeout: cdk.Duration.seconds(30),
213
+ runtime: aws_lambda.Runtime.NODEJS_16_X,
214
+
215
+ memorySize: 1024,
216
+
217
+ code: aws_lambda.Code.fromAsset(
218
+ path.join(stackProps.apiBuildPath, 'lambdaEventOriginRequest'),
219
+ ),
220
+ handler: 'index.executeEventOriginRequest',
221
+ },
222
+ );
223
+
224
+ // We don't need access to anything in the VR
225
+ grantReadToOwnedResources(ownedResourceSettings, edgeFunctionOR);
226
+
227
+ const wildcardPath = seo.path.replaceAll(/{(.+?)}/g, '*');
228
+
229
+ return {
230
+ pathPattern: wildcardPath,
231
+
232
+ // Update this to 24 hours
233
+ maxTtl: cdk.Duration.seconds(0),
234
+ minTtl: cdk.Duration.seconds(0),
235
+ defaultTtl: cdk.Duration.seconds(0),
236
+
237
+ lambdaFunctionAssociations: [
238
+ {
239
+ includeBody: true,
240
+ eventType: aws_cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
241
+ lambdaFunction: edgeFunctionOR.currentVersion,
242
+ },
243
+ {
244
+ includeBody: false,
245
+ eventType: aws_cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
246
+ lambdaFunction: edgeFunctionVR.currentVersion,
247
+ },
248
+ ],
249
+ };
250
+ });
251
+
130
252
  // Create a cloud front distribution
131
253
  // TODO: use aws_cloudfront.Distribution
254
+ // TODO: Somehow expose query strings: Note ~
255
+ // (Lambda@Edge only) To access the query string in an origin request or origin response function,
256
+ // your cache policy or origin request policy must be set to All for Query strings.
132
257
  const distribution = new aws_cloudfront.CloudFrontWebDistribution(stack, 'cf-distribution', {
133
258
  originConfigs: [
134
259
  {
@@ -137,16 +262,7 @@ const createWebDistribution = (
137
262
  originAccessIdentity: websiteOAI,
138
263
  },
139
264
  behaviors: [
140
- ...[
141
- '/remoteEntry.js',
142
- '/index.js', // Add this if we need it
143
- '/index.html', // Add this if we need it
144
- ].map((pp) => ({
145
- pathPattern: pp,
146
- maxTtl: cdk.Duration.seconds(0),
147
- minTtl: cdk.Duration.seconds(0),
148
- defaultTtl: cdk.Duration.seconds(0),
149
- })),
265
+ ...cloudFrontBehaviors,
150
266
  {
151
267
  isDefaultBehavior: true,
152
268
  },
@@ -180,7 +296,7 @@ export class QPQPrototypeSingleServiceStack extends Stack {
180
296
  super(scope, id, {
181
297
  env: {
182
298
  account: props.account,
183
- region: props.region,
299
+ region: qpqWebServerUtils.getDeployRegion(props.qpqConfig),
184
300
  },
185
301
  });
186
302
 
@@ -191,8 +307,6 @@ export class QPQPrototypeSingleServiceStack extends Stack {
191
307
 
192
308
  const domainName = createApiDomainName(this, id, props);
193
309
 
194
- createWebDistribution(this, `${id}-web-dist`, props);
195
-
196
310
  const resourceName = (name: string) => {
197
311
  return `${settings.service}-${settings.environment}-${name}`;
198
312
  };
@@ -210,23 +324,20 @@ export class QPQPrototypeSingleServiceStack extends Stack {
210
324
  };
211
325
  });
212
326
 
213
- const ownedParameters = qpqCoreUtils.getOwnedParameters(props.qpqConfig).map((parameter) => {
214
- const realParameterName = `${parameter.key}-${settings.service}-${settings.environment}`;
215
- return {
216
- parameterName: parameter.key,
217
- realName: realParameterName,
218
- parameter: new aws_ssm.StringParameter(this, `${id}-parameter-${parameter.key}`, {
219
- parameterName: realParameterName,
220
- description: `${settings.environment}-${settings.service}`,
221
- stringValue: parameter.value,
222
-
223
- // No additional costs ~ 4k max size
224
- tier: aws_ssm.ParameterTier.STANDARD,
225
- }),
226
- };
227
- });
327
+ const ownedParameters = qpqCoreUtils
328
+ .getOwnedParameters(props.qpqConfig)
329
+ .map((parameter) =>
330
+ createServiceParameter(
331
+ this,
332
+ id,
333
+ settings.service,
334
+ settings.environment,
335
+ parameter.key,
336
+ parameter.value,
337
+ ),
338
+ );
228
339
 
229
- const buckets = qpqCoreUtils.getStorageDriveNames(props.qpqConfig).map((driveName) => {
340
+ const ownedBuckets = qpqCoreUtils.getStorageDriveNames(props.qpqConfig).map((driveName) => {
230
341
  const bucketName = resourceName(driveName);
231
342
 
232
343
  const bucket = new aws_s3.Bucket(this, bucketName, {
@@ -258,14 +369,21 @@ export class QPQPrototypeSingleServiceStack extends Stack {
258
369
  }),
259
370
  {},
260
371
  ),
261
- parameterNameMap: ownedParameters.reduce(
262
- (acc, os) => ({
263
- ...acc,
264
- [os.parameterName]: os.realName,
265
- }),
266
- {},
267
- ),
268
- resourceNameMap: buckets.reduce(
372
+ parameterNameMap: {
373
+ ...ownedParameters.reduce(
374
+ (acc, os) => ({
375
+ ...acc,
376
+ [os.parameterName]: os.realName,
377
+ }),
378
+ {},
379
+ ),
380
+ ['qpqRuntimeConfig']: getParameterName(
381
+ 'qpqRuntimeConfig',
382
+ settings.service,
383
+ settings.environment,
384
+ ),
385
+ },
386
+ resourceNameMap: ownedBuckets.reduce(
269
387
  (acc, b) => ({
270
388
  ...acc,
271
389
  [b.driveName]: b.bucketName,
@@ -274,6 +392,31 @@ export class QPQPrototypeSingleServiceStack extends Stack {
274
392
  ),
275
393
  };
276
394
 
395
+ // Build the param that stores the config
396
+ // Note: The mapping was done manually previously inside the value
397
+ ownedParameters.push(
398
+ createServiceParameter(
399
+ this,
400
+ id,
401
+ settings.service,
402
+ settings.environment,
403
+ 'qpqRuntimeConfig',
404
+ JSON.stringify(runtimeConfigLambdaConfig),
405
+ ),
406
+ );
407
+
408
+ const ownedResourceSettings: OwnedResourceSettings = {
409
+ ownedBuckets,
410
+ ownedParameters,
411
+ ownedSecrets,
412
+ };
413
+
414
+ ////////////////////////////////////////////////////////////////////////////////////
415
+ // Finished building resources, now we can build the lambdas
416
+ ////////////////////////////////////////////////////////////////////////////////////
417
+
418
+ createWebDistribution(this, `${id}-web-dist`, props, ownedResourceSettings);
419
+
277
420
  const lambdaHandler = new aws_lambda.Function(
278
421
  this,
279
422
  `${settings.environment}-${settings.service}-lambda-rest`,
@@ -284,8 +427,8 @@ export class QPQPrototypeSingleServiceStack extends Stack {
284
427
  runtime: aws_lambda.Runtime.NODEJS_16_X,
285
428
  memorySize: 1024,
286
429
 
287
- code: aws_lambda.Code.fromAsset(props.apiBuildPath),
288
- handler: 'lambdaAPIGatewayEvent.executeAPIGatewayEvent',
430
+ code: aws_lambda.Code.fromAsset(path.join(props.apiBuildPath, 'lambdaAPIGatewayEvent')),
431
+ handler: 'index.executeAPIGatewayEvent',
289
432
 
290
433
  environment: {
291
434
  TABLES: JSON.stringify([]),
@@ -295,9 +438,7 @@ export class QPQPrototypeSingleServiceStack extends Stack {
295
438
  );
296
439
 
297
440
  // Grant access to the lambda
298
- buckets.forEach((b) => b.bucket.grantReadWrite(lambdaHandler));
299
- ownedSecrets.forEach((os) => os.secret.grantRead(lambdaHandler));
300
- ownedParameters.forEach((os) => os.parameter.grantRead(lambdaHandler));
441
+ grantReadToOwnedResources(ownedResourceSettings, lambdaHandler);
301
442
 
302
443
  // Create a rest api
303
444
  const api = new aws_apigateway.LambdaRestApi(
@@ -334,8 +475,8 @@ export class QPQPrototypeSingleServiceStack extends Stack {
334
475
  runtime: aws_lambda.Runtime.NODEJS_16_X,
335
476
  memorySize: 1024,
336
477
 
337
- code: aws_lambda.Code.fromAsset(props.apiBuildPath),
338
- handler: 'lambdaEventBridgeEvent.executeEventBridgeEvent',
478
+ code: aws_lambda.Code.fromAsset(path.join(props.apiBuildPath, 'lambdaEventBridgeEvent')),
479
+ handler: 'index.executeEventBridgeEvent',
339
480
 
340
481
  environment: {
341
482
  TABLES: JSON.stringify([]),
@@ -350,9 +491,7 @@ export class QPQPrototypeSingleServiceStack extends Stack {
350
491
  },
351
492
  );
352
493
 
353
- buckets.forEach((b) => b.bucket.grantReadWrite(schedulerFunction));
354
- ownedSecrets.forEach((os) => os.secret.grantRead(schedulerFunction));
355
- ownedParameters.forEach((os) => os.parameter.grantRead(schedulerFunction));
494
+ grantReadToOwnedResources(ownedResourceSettings, schedulerFunction);
356
495
 
357
496
  // EventBridge rule which runs every five minutes
358
497
  const cronRule = new aws_events.Rule(this, `${id}-se-cronrule-${index}`, {
@@ -0,0 +1,6 @@
1
+ import { ActionProcessorList } from 'quidproquo-core';
2
+ import { QPQAWSLambdaConfig } from 'quidproquo-actionprocessor-awslambda';
3
+
4
+ export type ActionProcessorListResolver = (
5
+ lambdaRuntimeConfig: QPQAWSLambdaConfig,
6
+ ) => ActionProcessorList;
@@ -17,7 +17,8 @@ import { createRuntime, askProcessEvent, ErrorTypeEnum } from 'quidproquo-core';
17
17
 
18
18
  import { APIGatewayEvent, Context } from 'aws-lambda';
19
19
 
20
- import { lambdaRuntimeConfig, ActionProcessorListResolver } from './lambdaConfig';
20
+ import { lambdaRuntimeConfig } from './lambdaConfig';
21
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
21
22
 
22
23
  // @ts-ignore - Special webpack loader
23
24
  import qpqDynamicModuleLoader from 'qpq-dynamic-loader!';
@@ -94,7 +95,6 @@ export const getAPIGatewayEventExecutor = (
94
95
  };
95
96
  }
96
97
 
97
- console.log(JSON.stringify(result));
98
98
  const code = ErrorTypeHttpResponseMap[result.error.errorType];
99
99
  return {
100
100
  statusCode: code || 500,
@@ -1,10 +1,5 @@
1
- import { ActionProcessorList } from 'quidproquo-core';
2
1
  import { QPQAWSLambdaConfig } from 'quidproquo-actionprocessor-awslambda';
3
2
 
4
3
  export const lambdaRuntimeConfig = JSON.parse(
5
4
  process.env.lambdaRuntimeConfig || '{}',
6
5
  ) as QPQAWSLambdaConfig;
7
-
8
- export type ActionProcessorListResolver = (
9
- lambdaRuntimeConfig: QPQAWSLambdaConfig,
10
- ) => ActionProcessorList;
@@ -14,7 +14,8 @@ import { createRuntime, askProcessEvent } from 'quidproquo-core';
14
14
 
15
15
  import { EventBridgeEvent, Context } from 'aws-lambda';
16
16
 
17
- import { lambdaRuntimeConfig, ActionProcessorListResolver } from './lambdaConfig';
17
+ import { lambdaRuntimeConfig } from './lambdaConfig';
18
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
18
19
 
19
20
  // @ts-ignore - Special webpack loader
20
21
  import qpqDynamicModuleLoader from 'qpq-dynamic-loader!';
@@ -0,0 +1,120 @@
1
+ import { coreActionProcessor, webserverActionProcessor } from 'quidproquo-actionprocessor-node';
2
+
3
+ import {
4
+ getCloudFrontOriginRequestEventActionProcessor,
5
+ getSystemActionProcessor,
6
+ getFileActionProcessor,
7
+ getConfigGetSecretActionProcessor,
8
+ getConfigGetParameterActionProcessor,
9
+ getConfigGetParametersActionProcessor,
10
+ awsLambdaUtils,
11
+ QPQAWSLambdaConfig,
12
+ DynamicModuleLoader,
13
+ getParameter,
14
+ } from 'quidproquo-actionprocessor-awslambda';
15
+
16
+ import { qpqWebServerUtils } from 'quidproquo-webserver';
17
+
18
+ import { createRuntime, askProcessEvent, ErrorTypeEnum, qpqCoreUtils } from 'quidproquo-core';
19
+
20
+ import { CloudFrontRequestEvent, Context } from 'aws-lambda';
21
+
22
+ import { ActionProcessorListResolver } from './actionProcessorListResolver';
23
+
24
+ // @ts-ignore - Special webpack loader
25
+ import qpqDynamicModuleLoader from 'qpq-dynamic-loader!';
26
+
27
+ // @ts-ignore - Special webpack loader
28
+ import qpqConfig from 'qpq-config-loader!';
29
+
30
+ // TODO: Make this a util or something based on server time or something..
31
+ const getDateNow = () => new Date().toISOString();
32
+
33
+ export const getOriginRequestEventExecutor = (
34
+ dynamicModuleLoader: DynamicModuleLoader,
35
+ getCustomActionProcessors: ActionProcessorListResolver = () => ({}),
36
+ ) => {
37
+ return async (event: CloudFrontRequestEvent, context: Context) => {
38
+ // Don't run the lambda if we are not a bot
39
+ if (event.Records[0].cf.request.headers['x-qpq-is-bot'][0].value !== 'true') {
40
+ return event.Records[0].cf.request;
41
+ }
42
+
43
+ // TODO: Make this error safe
44
+ const service = qpqCoreUtils.getAppName(qpqConfig);
45
+ const environment = qpqCoreUtils.getAppFeature(qpqConfig);
46
+ const param = await getParameter(
47
+ `qpqRuntimeConfig-${service}-${environment}`,
48
+ qpqWebServerUtils.getDeployRegion(qpqConfig),
49
+ );
50
+ const lambdaRuntimeConfig: QPQAWSLambdaConfig = JSON.parse(param);
51
+ // //////////////////////////
52
+
53
+ // Build a processor for the session and stuff
54
+ // Remove the non route ones ~ let the story execute action add them
55
+ const storyActionProcessor = {
56
+ ...coreActionProcessor,
57
+ ...webserverActionProcessor,
58
+
59
+ ...getCloudFrontOriginRequestEventActionProcessor(lambdaRuntimeConfig.qpqConfig),
60
+ ...getConfigGetSecretActionProcessor(lambdaRuntimeConfig),
61
+ ...getConfigGetParameterActionProcessor(lambdaRuntimeConfig),
62
+ ...getConfigGetParametersActionProcessor(lambdaRuntimeConfig),
63
+ ...getSystemActionProcessor(dynamicModuleLoader),
64
+ ...getFileActionProcessor(lambdaRuntimeConfig),
65
+
66
+ ...getCustomActionProcessors(lambdaRuntimeConfig),
67
+ };
68
+
69
+ const logger = async (result: any) => {};
70
+
71
+ // Run the callback
72
+
73
+ const resolveStory = createRuntime(
74
+ {},
75
+ storyActionProcessor,
76
+ getDateNow,
77
+ logger,
78
+ awsLambdaUtils.randomGuid,
79
+ );
80
+
81
+ const result = await resolveStory(askProcessEvent, [event, context]);
82
+
83
+ // Run the callback
84
+ if (!result.error) {
85
+ // TODO: Get typing to work here.
86
+ if (result.result.fallbackToCDN) {
87
+ // to forward the result to the origin, just return the request
88
+ // it has the origin details.
89
+ return event.Records[0].cf.request;
90
+ }
91
+
92
+ const headers = result.result.headers || {};
93
+ const responseHeaders = Object.keys(headers).reduce(
94
+ (acc, header) => ({ ...acc, [header]: [{ value: headers[header] }] }),
95
+ {},
96
+ );
97
+
98
+ return {
99
+ status: `${result.result.status}`,
100
+ statusDescription: 'OK',
101
+ body: result.result.body,
102
+ headers: responseHeaders,
103
+ };
104
+ }
105
+
106
+ // CloudFront seems to throw an error when you send back a 404 or something, so we will just hit the normal origin
107
+ return event.Records[0].cf.request;
108
+
109
+ // const code = ErrorTypeHttpResponseMap[result.error.errorType];
110
+ // return {
111
+ // statusCode: code || 500,
112
+ // statusDescription: result.error.errorType,
113
+ // body: JSON.stringify(result.error),
114
+ // headers: {},
115
+ // };
116
+ };
117
+ };
118
+
119
+ // Default executor
120
+ export const executeEventOriginRequest = getOriginRequestEventExecutor(qpqDynamicModuleLoader);
@@ -0,0 +1,28 @@
1
+ import { CloudFrontRequestEvent, Context } from 'aws-lambda';
2
+
3
+ export const getViewerRequestEventExecutor = () => {
4
+ return async (event: CloudFrontRequestEvent, context: Context) => {
5
+ const request = event.Records[0].cf.request;
6
+ const headers = request.headers;
7
+ const customUserAgentHeaderName = 'x-qpq-is-bot';
8
+ const userAgent = headers['user-agent'][0]?.value || 'unknown';
9
+
10
+ let isBot = userAgent.match(/bot|crawl|spider|slurp|facebot|facebookexternalhit/i);
11
+
12
+ return {
13
+ ...request,
14
+ headers: {
15
+ ...request.headers,
16
+ [customUserAgentHeaderName]: [
17
+ {
18
+ key: customUserAgentHeaderName,
19
+ value: isBot ? 'true' : 'false',
20
+ },
21
+ ],
22
+ },
23
+ };
24
+ };
25
+ };
26
+
27
+ // Default executor
28
+ export const executeEventViewerRequest = getViewerRequestEventExecutor();