diff --git a/typescript/s3-sns-filter-lambda/.eslintrc.json b/typescript/s3-sns-filter-lambda/.eslintrc.json new file mode 100644 index 0000000000..92124a0968 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/.eslintrc.json @@ -0,0 +1,76 @@ +{ + "extends": [ + "eslint:recommended", + "eslint-config-airbnb-base", + "airbnb-typescript", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "env": { + "es2021": true, + "node": true + }, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "prettier"], + "rules": { + "@typescript-eslint/no-unused-vars": "error", + "import/prefer-default-export": "off", + "no-tabs": ["off"], + "max-len": ["off"], + "no-await-in-loop": "off", + "react/jsx-filename-extension": "off", + "class-methods-use-this": "off", + "no-new": "off", + "no-param-reassign": "off", + "@typescript-eslint/quotes": [ + "error", + "single", + { + "avoidEscape": true + } + ], + "@typescript-eslint/comma-dangle": ["error", "never"], + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/lines-between-class-members": [ + "error", + "always", + { + "exceptAfterSingleLine": true + } + ], + "prettier/prettier": [ + "error", + { + "tabWidth": 2 + } + ] + }, + "overrides": [ + { + "files": ["*.spec.{js,ts}"], + "env": { + "mocha": true, + "node": true + }, + "rules": { + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-throw-literal": "off" + } + } + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "ignorePatterns": [ + "dist", + "node_modules", + ".eslintrc.js", + "jest.config.js", + "functions", + "cdk.out" + ] +} diff --git a/typescript/s3-sns-filter-lambda/.gitignore b/typescript/s3-sns-filter-lambda/.gitignore new file mode 100644 index 0000000000..a51787ebda --- /dev/null +++ b/typescript/s3-sns-filter-lambda/.gitignore @@ -0,0 +1,116 @@ +# Created by https://www.gitignore.io/api/osx,node,linux,windows + +### Linux ### +*~ +# Temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* +# KDE directory preferences +.directory +# Linux trash folder which might appear on any partition or disk +.Trash-* +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +# Runtime data +pids +*.pid +*.seed +*.pid.lock +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov +# Coverage directory used by tools like istanbul +coverage +# nyc test coverage +.nyc_output +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt +# Bower dependency directory (https://bower.io/) +bower_components +# node-waf configuration +.lock-wscript +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release +# Dependency directories +node_modules/ +jspm_packages/ +# Typescript v1 declaration files +typings/ +# Optional npm cache directory +.npm +# npm list directory +target +# Optional eslint cache +.eslintcache +# Optional REPL history +.node_repl_history +# Output of 'npm pack' +*.tgz +# Yarn Integrity file +.yarn-integrity +# dotenv environment variables file +.env + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db +# Folder config file +Desktop.ini +# Recycle Bin used on file shares +$RECYCLE.BIN/ +# Windows Installer files +*.cab +*.msi +*.msm +*.msp +# Windows shortcuts +*.lnk + +# AWS SAM and CLI directories +.aws/ +.aws-sam/ + +# End of https://www.gitignore.io/api/osx,node,linux,windows + +# Custom entries +test.ts +db.sqlite +.turbo +*.sqlite3 +output +samconfig.toml +build +.vscode +cdk.out +cdk.context.json diff --git a/typescript/s3-sns-filter-lambda/.prettierignore b/typescript/s3-sns-filter-lambda/.prettierignore new file mode 100644 index 0000000000..b5b74b15f3 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/.prettierignore @@ -0,0 +1 @@ +cdk.out diff --git a/typescript/s3-sns-filter-lambda/.prettierrc.json b/typescript/s3-sns-filter-lambda/.prettierrc.json new file mode 100644 index 0000000000..2a76e61cb0 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/.prettierrc.json @@ -0,0 +1,22 @@ +{ + "printWidth": 85, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "always", + "proseWrap": "always", + "plugins": [ + "prettier-plugin-packagejson", + "@trivago/prettier-plugin-sort-imports" + ], + "importOrder": ["^dotenv/config$", "", "^[./]"], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "importOrderCaseInsensitive": false +} diff --git a/typescript/s3-sns-filter-lambda/README.md b/typescript/s3-sns-filter-lambda/README.md new file mode 100644 index 0000000000..026537bf33 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/README.md @@ -0,0 +1,50 @@ +# S3, SNS, Lambda Integration + +This AWS CDK configuration sets up an automated workflow to trigger a specific Lambda +function based on the prefix of files uploaded to Amazon S3. Using the SNS service +with a filter policy, it directs events to the appropriate Lambda function +efficiently. + +## Get Started + +1. Install AWS CDK and Bootstrap Your AWS Account in a Specific Region + + ``` + npm install -g aws-cdk + cdk bootstrap + ``` + +2. Create a `.env` File with Your AWS Account ID + + ``` + AWS_ACCOUNT_ID=YOUR_ACCOUNT_ID + ``` + +3. Install Dependencies + + 1. Run the following command in the project root to install all dependencies: + + ``` + yarn + ``` + + 2. Then navigate to the functions folder to install the required node modules for + Lambda functions: + ``` + cd ./functions yarn + ``` + +4. Deploy to AWS + + - Use this command to deploy all stacks to AWS: + + ``` + cdk deploy --all + ``` + +## Try it out + +After running the above commands, an S3 bucket will be created. Upload a file to one +of the designated folders (e.g., folder1 or folder2). Depending on the folder, the +corresponding Lambda function (e.g., lambda1 or lambda2) will be triggered +automatically. diff --git a/typescript/s3-sns-filter-lambda/cdk.json b/typescript/s3-sns-filter-lambda/cdk.json new file mode 100644 index 0000000000..af8bf952bf --- /dev/null +++ b/typescript/s3-sns-filter-lambda/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "ts-node main.ts" +} diff --git a/typescript/s3-sns-filter-lambda/config/custom-environment-variables.json b/typescript/s3-sns-filter-lambda/config/custom-environment-variables.json new file mode 100644 index 0000000000..d4cdbb878f --- /dev/null +++ b/typescript/s3-sns-filter-lambda/config/custom-environment-variables.json @@ -0,0 +1,7 @@ +{ + "nodeEnv": "NODE_ENV", + "aws": { + "region": "AWS_REGION", + "accountId": "AWS_ACCOUNT_ID" + } +} diff --git a/typescript/s3-sns-filter-lambda/config/default.json b/typescript/s3-sns-filter-lambda/config/default.json new file mode 100644 index 0000000000..423b18a259 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/config/default.json @@ -0,0 +1,6 @@ +{ + "nodeEnv": "qa", + "aws": { + "region": "us-east-1" + } +} diff --git a/typescript/s3-sns-filter-lambda/config/develop.json b/typescript/s3-sns-filter-lambda/config/develop.json new file mode 100644 index 0000000000..b898c413e4 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/config/develop.json @@ -0,0 +1,3 @@ +{ + "nodeEnv": "develop" +} diff --git a/typescript/s3-sns-filter-lambda/config/prod.json b/typescript/s3-sns-filter-lambda/config/prod.json new file mode 100644 index 0000000000..18b834915f --- /dev/null +++ b/typescript/s3-sns-filter-lambda/config/prod.json @@ -0,0 +1,3 @@ +{ + "nodeEnv": "production" +} diff --git a/typescript/s3-sns-filter-lambda/constants/Buckets.ts b/typescript/s3-sns-filter-lambda/constants/Buckets.ts new file mode 100644 index 0000000000..c95456f7ee --- /dev/null +++ b/typescript/s3-sns-filter-lambda/constants/Buckets.ts @@ -0,0 +1,3 @@ +export enum Buckets { + ServiceBucket = 'ServiceBucket' +} diff --git a/typescript/s3-sns-filter-lambda/constants/Stacks.ts b/typescript/s3-sns-filter-lambda/constants/Stacks.ts new file mode 100644 index 0000000000..24d28220df --- /dev/null +++ b/typescript/s3-sns-filter-lambda/constants/Stacks.ts @@ -0,0 +1,8 @@ +import { ResourcePrefix } from './constants'; + +export const Stacks = { + LambdaStack: `${ResourcePrefix}-lambda-stack`, + RoleStack: `${ResourcePrefix}-role-stack`, + StorageStack: `${ResourcePrefix}-storage-stack`, + SnsStack: `${ResourcePrefix}-sns-stack` +}; diff --git a/typescript/s3-sns-filter-lambda/constants/constants.ts b/typescript/s3-sns-filter-lambda/constants/constants.ts new file mode 100644 index 0000000000..353c6cdc64 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/constants/constants.ts @@ -0,0 +1,6 @@ +import { config } from '../utils'; + +const app = 's3SnsLambda'; +const { accountId } = config.aws; + +export const ResourcePrefix = `${app}-${config.nodeEnv}-${accountId}`; diff --git a/typescript/s3-sns-filter-lambda/constants/index.ts b/typescript/s3-sns-filter-lambda/constants/index.ts new file mode 100644 index 0000000000..beaa173904 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/constants/index.ts @@ -0,0 +1,3 @@ +export * from './Buckets'; +export * from './constants'; +export * from './Stacks'; diff --git a/typescript/s3-sns-filter-lambda/functions/package.json b/typescript/s3-sns-filter-lambda/functions/package.json new file mode 100644 index 0000000000..4d51e7af46 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/functions/package.json @@ -0,0 +1,9 @@ +{ + "name": "functions", + "version": "1.0.0", + "license": "ISC", + "author": "ken.yip", + "dependencies": { + "@aws-sdk/client-sns": "^3.691.0" + } +} diff --git a/typescript/s3-sns-filter-lambda/main.ts b/typescript/s3-sns-filter-lambda/main.ts new file mode 100644 index 0000000000..f22e7028e2 --- /dev/null +++ b/typescript/s3-sns-filter-lambda/main.ts @@ -0,0 +1,26 @@ +import 'dotenv/config'; + +import { App, Environment } from 'aws-cdk-lib'; + +import { Stacks } from './constants'; +import { LambdaStack, RoleStack, SnsStack, StorageStack } from './stacks'; +import { config } from './utils'; + +const app = new App(); + +const env: Environment = { + account: config.aws.accountId, + region: config.aws.region +}; + +const roleStack = new RoleStack(app, Stacks.RoleStack, { env }); +const storageStack = new StorageStack(app, Stacks.StorageStack, { env }); +const snsStack = new SnsStack(app, Stacks.SnsStack, { env }); + +new LambdaStack(app, Stacks.LambdaStack, { + env, + serviceBucketArn: storageStack.serviceBucket.bucketArn, + s3TriggerSnsTopicArn: snsStack.s3TriggerSnsTopic.topicArn, + s3ListenerRoleArn: roleStack.s3ListenerRole.roleArn, + defaultLambdaRoleArn: roleStack.defaultLambdaRole.roleArn +});