Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions backend/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
27 changes: 27 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run bundle` bundle lambda handlers with custom esbuild config
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template

## Task 3 Artifacts

- Swagger/OpenAPI documentation: `swagger/product-service.yaml`
- Unit tests for handlers:
- `test/getProductsList.test.ts`
- `test/getProductsById.test.ts`

### Product Service API

- `GET /product/available`
- `GET /product/{productId}`
13 changes: 13 additions & 0 deletions backend/bin/backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env node

import * as cdk from 'aws-cdk-lib';

import { ProductServiceStack }
from '../lib/product-service-stack';

const app = new cdk.App();

new ProductServiceStack(
app,
'ProductServiceStack'
);
105 changes: 105 additions & 0 deletions backend/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"app": "npx ts-node --prefer-ts-exts bin/backend.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-signer:signingProfileNamePassedToCfn": true,
"@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-eks:useNativeOidcProvider": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/core:explicitStackTags": true,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
"@aws-cdk/core:aspectPrioritiesMutating": true,
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
"@aws-cdk/aws-lambda:useCdkManagedLogGroup": true,
"@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": true,
"@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": true,
"@aws-cdk/aws-route53-patterns:useDistribution": true,
"@aws-cdk/aws-cloudfront:defaultFunctionRuntimeV2_0": true,
"@aws-cdk/aws-elasticloadbalancingv2:usePostQuantumTlsPolicy": true
}
}
7 changes: 7 additions & 0 deletions backend/dist/handlers/getProductsById.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions backend/dist/handlers/getProductsList.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
setupFilesAfterEnv: ['aws-cdk-lib/testhelpers/jest-autoclean'],
};
20 changes: 20 additions & 0 deletions backend/lambda/data/products.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const products = [
{
id: "1",
title: "Laptop",
description: "Gaming laptop",
price: 1200,
},
{
id: "2",
title: "Phone",
description: "Smartphone",
price: 700,
},
{
id: "3",
title: "Headphones",
description: "Wireless headphones",
price: 200,
},
];
39 changes: 39 additions & 0 deletions backend/lambda/handlers/getProductsById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { products } from "../data/products";

export const handler = async (event: any) => {
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type,Authorization",
"Access-Control-Allow-Methods": "GET,OPTIONS",
};

const productId = event?.pathParameters?.productId;

if (!productId) {
return {
statusCode: 400,
headers: corsHeaders,
body: JSON.stringify({
message: "Missing productId",
}),
};
}

const product = products.find((p) => p.id === productId);

if (!product) {
return {
statusCode: 404,
headers: corsHeaders,
body: JSON.stringify({
message: "Product not found",
}),
};
}

return {
statusCode: 200,
headers: corsHeaders,
body: JSON.stringify(product),
};
};
13 changes: 13 additions & 0 deletions backend/lambda/handlers/getProductsList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { products } from "../data/products";

export const handler = async () => {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type,Authorization",
"Access-Control-Allow-Methods": "GET,OPTIONS",
},
body: JSON.stringify(products),
};
};
60 changes: 60 additions & 0 deletions backend/lib/product-service-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

import * as lambda from "aws-cdk-lib/aws-lambda";
import * as apigateway from "aws-cdk-lib/aws-apigateway";

export class ProductServiceStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);

const getProductsList = new lambda.Function(this, "GetProductsList", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "handlers/getProductsList.handler",
code: lambda.Code.fromAsset("lambda"),
});

const getProductsById = new lambda.Function(this, "GetProductsById", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "handlers/getProductsById.handler",
code: lambda.Code.fromAsset("lambda"),
});

const api = new apigateway.RestApi(this, "ProductsApi", {
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: ["GET", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization"],
},
});

const product = api.root.addResource("product");
const available = product.addResource("available");

available.addMethod(
"GET",
new apigateway.LambdaIntegration(getProductsList),
);

const singleProduct = product.addResource("{productId}");

singleProduct.addMethod(
"GET",
new apigateway.LambdaIntegration(getProductsById),
);

// Backward-compatible routes in case any client still uses /products/*.
const products = api.root.addResource("products");
products.addMethod(
"GET",
new apigateway.LambdaIntegration(getProductsList),
);
products
.addResource("{productId}")
.addMethod("GET", new apigateway.LambdaIntegration(getProductsById));

new cdk.CfnOutput(this, "ProductsApiUrl", {
value: api.url,
});
}
}
Loading