├── .npmignore ├── jest.config.js ├── .gitignore ├── CODE_OF_CONDUCT.md ├── README copy.md ├── README.md ├── events ├── saveTranslation.json ├── getAllTranslations.json ├── getTranslation.json └── putTranslation.json ├── src ├── get-translation │ ├── package.json │ └── app.js ├── save-translation │ ├── package.json │ └── app.js └── put-translation │ ├── package.json │ └── app.js ├── test └── cdk-day.test.ts ├── tsconfig.json ├── cdk.json ├── LICENSE ├── LICENSE copy ├── bin └── cdk-day.ts ├── package.json ├── CONTRIBUTING.md └── lib └── cdk-day-stack.ts /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/test'], 3 | testMatch: ['**/*.test.ts'], 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !jest.config.js 2 | *.d.ts 3 | node_modules 4 | 5 | # CDK asset staging directory 6 | .cdk.staging 7 | cdk.out 8 | .aws-sam 9 | response.json 10 | locals.json -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /README copy.md: -------------------------------------------------------------------------------- 1 | # Better together: AWS SAM and AWS CDK 2 | 3 | This repository contains a serverless application for translating text to multiple languages. 4 | Full instructions for using this application are found in the blog [Better together: AWS SAM and AWS CDK](https://aws.amazon.com/blogs/compute/better-together-aws-sam-and-aws-cdk/) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Better together: AWS SAM and AWS CDK 2 | 3 | This repository contains a serverless application for translating text to multiple languages. 4 | Full instructions for using this application are found in the blog [Better together: AWS SAM and AWS CDK](https://aws.amazon.com/blogs/compute/better-together-aws-sam-and-aws-cdk/) 5 | 6 | -------------------------------------------------------------------------------- /events/saveTranslation.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8f549775-c547-255b-f2ee-84e60a4da31c", 4 | "detail-type": "translation", 5 | "source": "website", 6 | "account": "088483494489", 7 | "time": "2021-04-27T04:32:36Z", 8 | "region": "us-west-2", 9 | "resources": [], 10 | "detail": { 11 | "language": "en", 12 | "translation": "This is my text", 13 | "id": "12345" 14 | } 15 | } -------------------------------------------------------------------------------- /src/get-translation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "translate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Eric Johnson", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "@aws-sdk/client-dynamodb": "^3.13.1", 13 | "@aws-sdk/util-dynamodb": "^3.13.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/save-translation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "translate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Eric Johnson", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "@aws-sdk/client-dynamodb": "^3.13.1", 13 | "@aws-sdk/util-dynamodb": "^3.13.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/put-translation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "translate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Eric Johnson", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "@aws-sdk/client-eventbridge": "^3.13.1", 13 | "@aws-sdk/client-translate": "^3.13.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/cdk-day.test.ts: -------------------------------------------------------------------------------- 1 | import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; 2 | import * as cdk from '@aws-cdk/core'; 3 | import * as CdkDay from '../lib/cdk-day-stack'; 4 | 5 | test('Empty Stack', () => { 6 | const app = new cdk.App(); 7 | // WHEN 8 | const stack = new CdkDay.CdkDayStack(app, 'MyTestStack'); 9 | // THEN 10 | expectCDK(stack).to(matchTemplate({ 11 | "Resources": {} 12 | }, MatchStyle.EXACT)) 13 | }); 14 | -------------------------------------------------------------------------------- /src/save-translation/app.js: -------------------------------------------------------------------------------- 1 | /*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | * SPDX-License-Identifier: MIT-0 3 | */ 4 | 5 | const {DynamoDB} = require("@aws-sdk/client-dynamodb") 6 | const { marshall } = require("@aws-sdk/util-dynamodb"); 7 | const dynamoClient = new DynamoDB; 8 | const TableName = process.env.TRANSLATE_TABLE 9 | 10 | exports.handler = async function (event) { 11 | const Item = marshall(event.detail) 12 | 13 | console.log(JSON.stringify(event)) 14 | 15 | try { 16 | return dynamoClient.putItem({TableName, Item}); 17 | } catch (error){ 18 | throw new Error(error.message) 19 | } 20 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cdk-day.ts", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "@aws-cdk/core:enableStackNameDuplicates": "true", 6 | "aws-cdk:enableDiffNoFail": "true", 7 | "@aws-cdk/core:stackRelativeExports": "true", 8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, 10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true, 11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, 12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, 13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /LICENSE copy: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /bin/cdk-day.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import { CdkDayStack } from '../lib/cdk-day-stack'; 5 | 6 | const app = new cdk.App(); 7 | new CdkDayStack(app, 'CdkDayStack', { 8 | /* If you don't specify 'env', this stack will be environment-agnostic. 9 | * Account/Region-dependent features and context lookups will not work, 10 | * but a single synthesized template can be deployed anywhere. */ 11 | 12 | /* Uncomment the next line to specialize this stack for the AWS Account 13 | * and Region that are implied by the current CLI configuration. */ 14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 15 | 16 | /* Uncomment the next line if you know exactly what Account and Region you 17 | * want to deploy the stack to. */ 18 | // env: { account: '123456789012', region: 'us-east-1' }, 19 | 20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 21 | }); 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-day", 3 | "version": "0.1.0", 4 | "license": "MIT-0", 5 | "bin": { 6 | "cdk-day": "bin/cdk-day.js" 7 | }, 8 | "scripts": { 9 | "build": "tsc", 10 | "watch": "tsc -w", 11 | "test": "jest", 12 | "cdk": "cdk" 13 | }, 14 | "devDependencies": { 15 | "@aws-cdk/assert": "1.180.0", 16 | "@types/jest": "^26.0.10", 17 | "@types/node": "10.17.27", 18 | "aws-cdk": "^1.172.0", 19 | "jest": "^26.4.2", 20 | "ts-jest": "^26.2.0", 21 | "ts-node": "^9.0.0", 22 | "typescript": "~3.9.7" 23 | }, 24 | "dependencies": { 25 | "@aws-cdk/aws-apigatewayv2": "^1.180.0", 26 | "@aws-cdk/aws-apigatewayv2-integrations": "^1.180.0", 27 | "@aws-cdk/aws-dynamodb": "^1.180.0", 28 | "@aws-cdk/aws-events": "^1.180.0", 29 | "@aws-cdk/aws-events-targets": "^1.180.0", 30 | "@aws-cdk/aws-iam": "^1.180.0", 31 | "@aws-cdk/aws-lambda": "^1.180.0", 32 | "@aws-cdk/aws-lambda-nodejs": "^1.180.0", 33 | "@aws-cdk/core": "1.180.0", 34 | "esbuild": "^0.11.13", 35 | "source-map-support": "^0.5.16" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /events/getAllTranslations.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "routeKey": "GET /{id}", 4 | "rawPath": "/12345", 5 | "rawQueryString": "", 6 | "headers": { 7 | "accept": "*/*", 8 | "accept-encoding": "gzip, deflate, br", 9 | "cache-control": "no-cache", 10 | "content-length": "0", 11 | "host": "5gduqjk6dg.execute-api.us-west-2.amazonaws.com", 12 | "postman-token": "087ab61e-7694-4c79-bb0a-2f3416fce34b", 13 | "user-agent": "PostmanRuntime/7.26.10", 14 | "x-amzn-trace-id": "Root=1-60879fde-4ff859b57ab35aea593afc91", 15 | "x-forwarded-for": "69.21.110.171", 16 | "x-forwarded-port": "443", 17 | "x-forwarded-proto": "https" 18 | }, 19 | "requestContext": { 20 | "accountId": "088483494489", 21 | "apiId": "5gduqjk6dg", 22 | "domainName": "5gduqjk6dg.execute-api.us-west-2.amazonaws.com", 23 | "domainPrefix": "5gduqjk6dg", 24 | "http": { 25 | "method": "GET", 26 | "path": "/12345", 27 | "protocol": "HTTP/1.1", 28 | "sourceIp": "69.21.110.171", 29 | "userAgent": "PostmanRuntime/7.26.10" 30 | }, 31 | "requestId": "ebXqygC-PHcEJ5A=", 32 | "routeKey": "GET /{id}", 33 | "stage": "$default", 34 | "time": "27/Apr/2021:05:23:42 +0000", 35 | "timeEpoch": 1619501022510 36 | }, 37 | "isBase64Encoded": false 38 | } -------------------------------------------------------------------------------- /events/getTranslation.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "routeKey": "GET /{id}", 4 | "rawPath": "/12345", 5 | "rawQueryString": "", 6 | "headers": { 7 | "accept": "*/*", 8 | "accept-encoding": "gzip, deflate, br", 9 | "cache-control": "no-cache", 10 | "content-length": "0", 11 | "host": "5gduqjk6dg.execute-api.us-west-2.amazonaws.com", 12 | "postman-token": "087ab61e-7694-4c79-bb0a-2f3416fce34b", 13 | "user-agent": "PostmanRuntime/7.26.10", 14 | "x-amzn-trace-id": "Root=1-60879fde-4ff859b57ab35aea593afc91", 15 | "x-forwarded-for": "69.21.110.171", 16 | "x-forwarded-port": "443", 17 | "x-forwarded-proto": "https" 18 | }, 19 | "requestContext": { 20 | "accountId": "088483494489", 21 | "apiId": "5gduqjk6dg", 22 | "domainName": "5gduqjk6dg.execute-api.us-west-2.amazonaws.com", 23 | "domainPrefix": "5gduqjk6dg", 24 | "http": { 25 | "method": "GET", 26 | "path": "/12345", 27 | "protocol": "HTTP/1.1", 28 | "sourceIp": "69.21.110.171", 29 | "userAgent": "PostmanRuntime/7.26.10" 30 | }, 31 | "requestId": "ebXqygC-PHcEJ5A=", 32 | "routeKey": "GET /{id}", 33 | "stage": "$default", 34 | "time": "27/Apr/2021:05:23:42 +0000", 35 | "timeEpoch": 1619501022510 36 | }, 37 | "pathParameters": { 38 | "id": "12345" 39 | }, 40 | "isBase64Encoded": false 41 | } -------------------------------------------------------------------------------- /events/putTranslation.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "routeKey": "POST /", 4 | "rawPath": "/", 5 | "rawQueryString": "", 6 | "headers": { 7 | "accept": "*/*", 8 | "accept-encoding": "gzip, deflate, br", 9 | "cache-control": "no-cache", 10 | "content-length": "67", 11 | "content-type": "application/json", 12 | "host": "5gduqjk6dg.execute-api.us-west-2.amazonaws.com", 13 | "postman-token": "e1e29d9d-389f-45da-b7f3-c5bdd873c916", 14 | "user-agent": "PostmanRuntime/7.26.10", 15 | "x-amzn-trace-id": "Root=1-6087880a-1c8111a52205e65b2c2e5e1a", 16 | "x-forwarded-for": "69.21.110.171", 17 | "x-forwarded-port": "443", 18 | "x-forwarded-proto": "https" 19 | }, 20 | "requestContext": { 21 | "accountId": "088483494489", 22 | "apiId": "5gduqjk6dg", 23 | "domainName": "5gduqjk6dg.execute-api.us-west-2.amazonaws.com", 24 | "domainPrefix": "5gduqjk6dg", 25 | "http": { 26 | "method": "POST", 27 | "path": "/", 28 | "protocol": "HTTP/1.1", 29 | "sourceIp": "69.21.110.171", 30 | "userAgent": "PostmanRuntime/7.26.10" 31 | }, 32 | "requestId": "12345", 33 | "routeKey": "POST /", 34 | "stage": "$default", 35 | "time": "27/Apr/2021:03:42:02 +0000", 36 | "timeEpoch": 1619494922394 37 | }, 38 | "body": "{\n \"text\":\"This is my text\",\n \"languages\": [\"de\",\"fr\"]\n}", 39 | "isBase64Encoded": false 40 | } -------------------------------------------------------------------------------- /src/get-translation/app.js: -------------------------------------------------------------------------------- 1 | /*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | * SPDX-License-Identifier: MIT-0 3 | */ 4 | 5 | const {DynamoDB} = require("@aws-sdk/client-dynamodb") 6 | const {marshall,unmarshall} = require("@aws-sdk/util-dynamodb"); 7 | const dynamoClient = new DynamoDB; 8 | const TableName = process.env.TRANSLATE_TABLE 9 | 10 | exports.getOne = async function (id) { 11 | let dynamoParams = { 12 | TableName, 13 | ExpressionAttributeValues: marshall({ 14 | ":i": id 15 | }), 16 | KeyConditionExpression: "id = :i", 17 | } 18 | return dynamoClient.query(dynamoParams) 19 | } 20 | 21 | exports.getAll = async function () { 22 | let dynamoParams = { 23 | TableName, 24 | ExpressionAttributeNames: { 25 | "#l": "language", 26 | }, 27 | ExpressionAttributeValues: marshall({ 28 | ":l": "en", 29 | }), 30 | FilterExpression: "#l = :l" 31 | } 32 | return dynamoClient.scan(dynamoParams) 33 | } 34 | 35 | exports.handler = async function (event) { 36 | let response 37 | try { 38 | if (event.pathParameters && event.pathParameters.id) 39 | response = await exports.getOne(event.pathParameters.id) 40 | else 41 | response = await exports.getAll() 42 | 43 | return {"Items": response.Items.map(item => { 44 | let data = unmarshall(item) 45 | // delete data.language 46 | return data 47 | })} 48 | } catch (error) { 49 | throw new Error(error.message) 50 | } 51 | } -------------------------------------------------------------------------------- /src/put-translation/app.js: -------------------------------------------------------------------------------- 1 | /*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | * SPDX-License-Identifier: MIT-0 3 | */ 4 | 5 | const {TranslateClient, TranslateTextCommand} = require("@aws-sdk/client-translate") 6 | const {EventBridgeClient, PutEventsCommand} = require("@aws-sdk/client-eventbridge") 7 | const translateClient = new TranslateClient(); 8 | const eventBridgeClient = new EventBridgeClient(); 9 | const translateCommand = new TranslateTextCommand(); 10 | const eventBridgeCommand = new PutEventsCommand(); 11 | 12 | exports.buildTranslationRequest = function (language, text){ 13 | let translateParams = { 14 | SourceLanguageCode: 'en', 15 | TargetLanguageCode: language, 16 | Text: text, 17 | } 18 | translateCommand.input = translateParams; 19 | return translateClient.send(translateCommand); 20 | } 21 | 22 | exports.buildEventBridgePackage = function(translations, id){ 23 | let entries = translations.map(item => { 24 | item['id'] = id 25 | return { 26 | Detail: JSON.stringify(item), 27 | DetailType: 'translation', 28 | EventBusName: process.env.TRANSLATE_BUS, 29 | Source: 'website' 30 | } 31 | }) 32 | return { 33 | Entries: entries 34 | } 35 | } 36 | 37 | exports.handler = async function (event) { 38 | let body = JSON.parse(event.body) 39 | let translateText = body.text 40 | let lang = body.languages; 41 | 42 | let translations = lang.map(item => { 43 | return exports.buildTranslationRequest(item, translateText) 44 | }) 45 | 46 | try { 47 | // get translations 48 | let translateResponse = await Promise.all(translations) 49 | let data = translateResponse.map(item => { 50 | return {"language": item.TargetLanguageCode, "translation": item.TranslatedText} 51 | }) 52 | data.push({"language": "en", "translation": translateText}) 53 | 54 | // send events to eventbridge 55 | eventBridgeCommand.input = exports.buildEventBridgePackage(data, event.requestContext.requestId); 56 | 57 | let ebresults = await eventBridgeClient.send(eventBridgeCommand); 58 | console.log(ebresults); 59 | 60 | return { "id":event.requestContext.requestId, "Items": data} 61 | 62 | } catch (error){ 63 | throw new Error(error.message) 64 | } 65 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /lib/cdk-day-stack.ts: -------------------------------------------------------------------------------- 1 | import { HttpApi, HttpMethod } from '@aws-cdk/aws-apigatewayv2'; 2 | import { LambdaProxyIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; 3 | import { AttributeType, Table } from '@aws-cdk/aws-dynamodb'; 4 | import { EventBus, Rule } from '@aws-cdk/aws-events'; 5 | import { LambdaFunction } from '@aws-cdk/aws-events-targets'; 6 | import { Policy, PolicyStatement } from '@aws-cdk/aws-iam'; 7 | import { Code, Function, Runtime, Tracing } from '@aws-cdk/aws-lambda'; 8 | import * as cdk from '@aws-cdk/core'; 9 | import { CfnOutput } from '@aws-cdk/core'; 10 | 11 | export class CdkDayStack extends cdk.Stack { 12 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 13 | super(scope, id, props); 14 | 15 | // ################################################### 16 | // Translation DDB table 17 | // ################################################### 18 | const translateTable = new Table(this, "TranslateTable", { 19 | partitionKey: {name: 'id', type: AttributeType.STRING}, 20 | sortKey: {name: 'language', type: AttributeType.STRING} 21 | }) 22 | 23 | // ################################################### 24 | // Translation EventBridge bus 25 | // ################################################### 26 | const translateBus = new EventBus(this, "TranslateBus", { 27 | eventBusName: "TranslateBus" 28 | }) 29 | 30 | // ################################################### 31 | // Put translation function 32 | // ################################################### 33 | const putTranslationFunction = new Function(this, "PutTranslationFunction", { 34 | runtime: Runtime.NODEJS_14_X, 35 | handler: 'app.handler', 36 | code: Code.fromAsset('src/put-translation'), 37 | tracing: Tracing.ACTIVE, 38 | timeout: cdk.Duration.seconds(10), 39 | environment: { 40 | 'TRANSLATE_BUS': translateBus.eventBusName 41 | } 42 | }) 43 | 44 | translateBus.grantPutEventsTo(putTranslationFunction) 45 | 46 | const translatePolicyStatement = new PolicyStatement({ 47 | actions: ['translate:TranslateText'], 48 | resources: ['*'] 49 | }) 50 | 51 | putTranslationFunction.role?.attachInlinePolicy( 52 | new Policy(this, "PutTranslatePolicy", { 53 | statements: [translatePolicyStatement] 54 | }) 55 | ) 56 | 57 | // ################################################### 58 | // Get translations function 59 | // ################################################### 60 | const getTranslationFunction = new Function(this, "GetTranslationFunction", { 61 | runtime: Runtime.NODEJS_14_X, 62 | handler: 'app.handler', 63 | code: Code.fromAsset('src/get-translation'), 64 | tracing: Tracing.ACTIVE, 65 | timeout: cdk.Duration.seconds(10), 66 | environment: { 67 | 'TRANSLATE_TABLE': translateTable.tableName 68 | } 69 | }) 70 | 71 | translateTable.grantReadData(getTranslationFunction) 72 | 73 | // ################################################### 74 | // Save translations function 75 | // ################################################### 76 | const saveTranslationFunction = new Function(this, "SaveTranslationFunction", { 77 | runtime: Runtime.NODEJS_14_X, 78 | handler: 'app.handler', 79 | code: Code.fromAsset('src/save-translation'), 80 | tracing: Tracing.ACTIVE, 81 | timeout: cdk.Duration.seconds(10), 82 | environment:{ 83 | 'TRANSLATE_TABLE': translateTable.tableName 84 | } 85 | }) 86 | 87 | translateTable.grantWriteData(saveTranslationFunction) 88 | 89 | // ################################################### 90 | // EventBridge Rule 91 | // ################################################### 92 | new Rule(this, "SaveTranslationRule", { 93 | eventBus: translateBus, 94 | eventPattern: {detailType: ["translation"]}, 95 | targets:[new LambdaFunction(saveTranslationFunction)] 96 | }) 97 | 98 | // ################################################### 99 | // API Gateway and routes 100 | // ################################################### 101 | const translateAPI = new HttpApi(this, "TranslateAPI") 102 | 103 | translateAPI.addRoutes({ 104 | path: '/', 105 | methods: [HttpMethod.POST], 106 | integration: new LambdaProxyIntegration({ 107 | handler: putTranslationFunction 108 | }) 109 | }) 110 | 111 | const getProxy = new LambdaProxyIntegration({ 112 | handler: getTranslationFunction 113 | }) 114 | 115 | translateAPI.addRoutes({ 116 | path: '/{id}', 117 | methods: [HttpMethod.GET], 118 | integration: getProxy 119 | }) 120 | 121 | translateAPI.addRoutes({ 122 | path: '/', 123 | methods: [HttpMethod.GET], 124 | integration: getProxy 125 | }) 126 | 127 | // ################################################### 128 | // Outputs 129 | // ################################################### 130 | new CfnOutput(this, 'API url', { 131 | value: translateAPI.url! 132 | }) 133 | new CfnOutput(this, 'Put Function Name', { 134 | value: putTranslationFunction.functionName 135 | }) 136 | new CfnOutput(this, 'Save Function Name', { 137 | value: saveTranslationFunction.functionName 138 | }) 139 | new CfnOutput(this, 'Get Function Name', { 140 | value: getTranslationFunction.functionName 141 | }) 142 | new CfnOutput(this, "Translation Bus", { 143 | value: translateBus.eventBusName 144 | }) 145 | new CfnOutput(this, "Translation Table", { 146 | value: translateTable.tableName 147 | }) 148 | } 149 | } 150 | --------------------------------------------------------------------------------