├── .gitignore ├── LICENSE ├── README.md ├── api ├── .gitignore ├── authorizer │ ├── handler.js │ ├── package-lock.json │ └── package.json ├── create-booking │ ├── handler.js │ ├── package-lock.json │ └── package.json ├── list-bookings │ ├── handler.js │ ├── package-lock.json │ └── package.json ├── login │ ├── handler.js │ ├── package-lock.json │ └── package.json ├── register │ ├── handler.js │ ├── package-lock.json │ └── package.json └── serverless.yml ├── assets └── logo-terraform.png ├── bookings-consumer ├── .gitignore ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml ├── deploy.sh ├── destroy.sh ├── email-notification ├── .gitignore ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml ├── sms-notification ├── .gitignore ├── handler.js ├── package-lock.json ├── package.json └── serverless.yml └── terraform ├── environments ├── dev │ ├── main.tf │ ├── provider.tf │ ├── secrets.auto.tfvars │ ├── variables.auto.tfvars │ └── variables.tf └── prod │ ├── main.tf │ ├── provider.tf │ ├── secrets.auto.tfvars │ ├── variables.auto.tfvars │ └── variables.tf └── infra ├── bookings ├── dynamodb-bookings.tf ├── iam-policy-attachment-bookings-stream-consumer.tf ├── iam-policy-attachment-create-booking.tf ├── iam-policy-attachment-list-bookings.tf ├── iam-policy-bookings-stream-consumer.tf ├── iam-policy-create-booking.tf ├── iam-policy-list-bookings.tf ├── iam-role-bookings-stream-consumer.tf ├── iam-role-create-booking.tf ├── iam-role-list-bookings.tf ├── templates │ ├── dynamodb-policy.tpl │ └── lambda-base-policy.tpl └── variables.tf ├── notifications ├── iam-policy-attachment-email.tf ├── iam-policy-attachment-sms.tf ├── iam-policy-email.tf ├── iam-policy-sms.tf ├── iam-role-email.tf ├── iam-role-sms.tf ├── sns-subscriptions.tf ├── sns.tf ├── sqs-email.tf ├── sqs-sms.tf ├── templates │ ├── lambda-base-policy.tpl │ ├── lambda-sqs-policy.tpl │ └── sqs-sns-policy.tpl └── variables.tf ├── system ├── ssm.tf └── variables.tf └── users ├── dynamodb-users.tf ├── iam-policy-attachment-login.tf ├── iam-policy-attachment-register.tf ├── iam-policy-login.tf ├── iam-policy-register.tf ├── iam-role-login.tf ├── iam-role-register.tf ├── ssm-jwt-secret.tf ├── templates ├── dynamodb-policy.tpl └── lambda-base-policy.tpl └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform* 2 | terraform.* 3 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eduardo Santana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Terraform 3 |

4 | 5 |

6 | Serverless booking software 7 |

8 | 9 |

Cloud-Native Serverless application developed with Hashicorp Terraform and Serverless Framework.

10 | 11 | ## Introduction 12 | 13 | The main purpose of this small event-driven project is to show how to use Terraform to provision immutable Cloud-Native infrastructure, along with the Serverless Framework. 14 | 15 | ## Infrastructure as Code (IaC) 👷🏻 16 | 17 | Terraform is an open-source Infrastructure as Code tool to manage cloud resources. It was mainly used to provision the core infrastructure at AWS, which was the chosen cloud provider.
18 | 19 | ### Resources 20 | 21 | These are the main infrastructure resources provisioned by Terraform to create a fully event-driven serverless architecture: 22 | 23 | - DynamoDB 24 | - Tables 25 | - Indexes 26 | - Streams 27 | - IAM 28 | - Roles 29 | - Policies 30 | - SNS topics 31 | - SQS 32 | - Queues 33 | - DQLs 34 | - Systems Manager 35 | - Parameter Store: this service is the key-point to make it possible to Serverless and Terraform communicate to each other. All the resources names and ARNs (IAM roles, policies, queues, tables, etc.) are exported to the Parameter Store as key-values by Terraform. Then, it is possible to access these keys via Serverless Framework and referecence IAM role statements or create environment variables for each Lambda function. 36 | 37 | ## Serverless ☁️ 38 | 39 | It is also possible to provision the resources mentioned previously with the Serverless Framework, however, in this project, I chose to separate responsabilities and only Terraform is used to manage infrastructure.
40 | 41 | Therefore, the main usage of Serverless in this project is to create the API gateway endpoints and Lambda functions triggered by API Gateway's events. 42 | 43 | ### Endpoints 44 | 45 | The API is really simple and has only four endpoints: 46 | 47 | - POST/users: register a new user. 48 | - POST/login: user authentication (returns a JWT token). 49 | - POST/bookings: register a new booking. 50 | - GET/bookings: list all the bookings (restricted to ADMIN users). 51 | 52 | Click on the button below and import the Insomnia workspace to do the API requests after the deployment step: 53 | 54 | [![Run in Insomnia}](https://insomnia.rest/images/run.svg)](https://insomnia.rest/run/?label=Terraform%20Serverless&uri=https%3A%2F%2Fgist.github.com%2Feduardo3g%2F0bc07cdc507e62b90c620b3eab50c1f2) 55 | 56 | ## Deploy 57 | 58 | ### Prerequisites 59 | 60 | First of all, you need an AWS account and create a user with programmatic admin access. Then, configure the AWS CLI on your workstation. 61 |
62 | 63 | Once you're done, clone this repository and move yourself to the root directory. 64 | 65 | ### Environment variables 66 | 67 | The API depends on two external services to send e-mails and SMSs when a new booking is registed in the DynamoDB table. Create accounts on each of them: 68 | - Zoho 69 | - Messagebird 70 | 71 |
72 | 73 | The last thing before the deployment is to define your environment variables of the `dev` stage. 74 | 75 | ```bash 76 | # Move to the Terraform development directory 77 | cd terraform/environments/dev 78 | 79 | # Open the file that contains the environment variables and update the values 80 | nano secrets.auto.tfvars 81 | ``` 82 | 83 | In order to keep things simple, I recommend only changing the following keys: 84 | - email_from: use the e-mail you created at Zoho (the one containing the domain @zohomail.com) 85 | - email_from_password: your password from Zoho 86 | - email_to: create a temporary e-mail box at TempMail (leave the window opened while you're testing) 87 | - message_bird_api_key: your test API Key from Messagebird (No SMSs will be sent with this key. Use the production one if you want to receive them.) 88 | - sms_phone_from: your cellphone number (e.g: "+55119...") 89 | - sms_phone_to: your cellphone number (e.g: "+55119...") 90 | 91 | ### Deploy 92 | 93 | Luckly, now you just need to run a single command and Terraform will do the magic for you 😛 94 | ```bash 95 | # Run the command bellow to avoid lack of permission to run the deploy shell script 96 | chmod u=rwx,g=r,o=r deploy.sh 97 | 98 | # Deploy everything to a development stage at AWS 99 | ./deploy.sh dev 100 | ``` 101 | 102 | You're supposed to see 53 resources created by Terraform, that is, all the resources I mentioned in previous sections (IAM roles, tables, queues, etc). 103 | 104 |
105 | 106 | In the other hand, the Serverless Framework will create the `dev-api` and 8 Lambda functions. 107 | 108 | ### Destroy 109 | 110 | Now that you've tested everything, feel free to delete all the resources at AWS. It's as simple as the deployment script: 111 | ```bash 112 | # Run the command bellow to avoid lack of permission to run the destroy shell script 113 | chmod u=rwx,g=r,o=r deploy.sh 114 | 115 | # Remove everything from the development stage at AWS 116 | ./destroy.sh dev 117 | ``` 118 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /api/authorizer/handler.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | exports.authorizer = function(event, context, callback) { 4 | const authHeader = event.authorizationToken; 5 | 6 | try { 7 | const [, token] = authHeader.split(' '); 8 | 9 | const user = jwt.verify(token, process.env.JWT_SECRET); 10 | callback(null, generatePolicy('user', 'Allow', event.methodArn, user)); 11 | } catch (exception) { 12 | console.log(exception); 13 | callback(null, generatePolicy('user', 'Deny', event.methodArn)); 14 | } 15 | }; 16 | 17 | const generatePolicy = function(principalId, effect, resource, user) { 18 | let authResponse = {}; 19 | 20 | authResponse.principalId = principalId; 21 | if (effect && resource) { 22 | let policyDocument = {}; 23 | policyDocument.Version = '2012-10-17'; 24 | policyDocument.Statement = []; 25 | let statementOne = {}; 26 | statementOne.Action = 'execute-api:Invoke'; 27 | statementOne.Effect = effect; 28 | statementOne.Resource = resource; 29 | policyDocument.Statement[0] = statementOne; 30 | authResponse.policyDocument = policyDocument; 31 | } 32 | 33 | if (user) { 34 | authResponse.context = user; 35 | } 36 | return authResponse; 37 | } -------------------------------------------------------------------------------- /api/authorizer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorizer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "buffer-equal-constant-time": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 10 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 11 | }, 12 | "ecdsa-sig-formatter": { 13 | "version": "1.0.11", 14 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 15 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 16 | "requires": { 17 | "safe-buffer": "^5.0.1" 18 | } 19 | }, 20 | "jsonwebtoken": { 21 | "version": "8.5.1", 22 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 23 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 24 | "requires": { 25 | "jws": "^3.2.2", 26 | "lodash.includes": "^4.3.0", 27 | "lodash.isboolean": "^3.0.3", 28 | "lodash.isinteger": "^4.0.4", 29 | "lodash.isnumber": "^3.0.3", 30 | "lodash.isplainobject": "^4.0.6", 31 | "lodash.isstring": "^4.0.1", 32 | "lodash.once": "^4.0.0", 33 | "ms": "^2.1.1", 34 | "semver": "^5.6.0" 35 | } 36 | }, 37 | "jwa": { 38 | "version": "1.4.1", 39 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 40 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 41 | "requires": { 42 | "buffer-equal-constant-time": "1.0.1", 43 | "ecdsa-sig-formatter": "1.0.11", 44 | "safe-buffer": "^5.0.1" 45 | } 46 | }, 47 | "jws": { 48 | "version": "3.2.2", 49 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 50 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 51 | "requires": { 52 | "jwa": "^1.4.1", 53 | "safe-buffer": "^5.0.1" 54 | } 55 | }, 56 | "lodash.includes": { 57 | "version": "4.3.0", 58 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 59 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 60 | }, 61 | "lodash.isboolean": { 62 | "version": "3.0.3", 63 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 64 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 65 | }, 66 | "lodash.isinteger": { 67 | "version": "4.0.4", 68 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 69 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 70 | }, 71 | "lodash.isnumber": { 72 | "version": "3.0.3", 73 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 74 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 75 | }, 76 | "lodash.isplainobject": { 77 | "version": "4.0.6", 78 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 79 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 80 | }, 81 | "lodash.isstring": { 82 | "version": "4.0.1", 83 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 84 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 85 | }, 86 | "lodash.once": { 87 | "version": "4.1.1", 88 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 89 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 90 | }, 91 | "ms": { 92 | "version": "2.1.3", 93 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 94 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 95 | }, 96 | "safe-buffer": { 97 | "version": "5.2.1", 98 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 99 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 100 | }, 101 | "semver": { 102 | "version": "5.7.1", 103 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 104 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /api/authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorizer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "jsonwebtoken": "^8.5.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /api/create-booking/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); 4 | AWS.config.update({ 5 | region: process.env.AWS_REGION, 6 | }); 7 | const documentClient = new AWS.DynamoDB.DocumentClient(); 8 | 9 | const { v4: uuidv4 } = require('uuid'); 10 | 11 | module.exports.create = async event => { 12 | const body = JSON.parse(event.body); 13 | 14 | await documentClient.put({ 15 | TableName: process.env.DYNAMODB_BOOKINGS, 16 | Item: { 17 | id: uuidv4(), 18 | date: body.date, 19 | user: event.requestContext.authorizer, 20 | } 21 | }).promise(); 22 | 23 | return { 24 | statusCode: 200, 25 | body: JSON.stringify({ message: 'The booking was created successfully' }), 26 | } 27 | }; -------------------------------------------------------------------------------- /api/create-booking/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-booking", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.817.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.817.0.tgz", 10 | "integrity": "sha512-DZIdWpkcqbqsCz0MEskHsyFaqc6Tk9XIFqXAg1AKHbOgC8nU45bz+Y2osX77pU01JkS/G7OhGtGmlKDrOPvFwg==", 11 | "dev": true, 12 | "requires": { 13 | "buffer": "4.9.2", 14 | "events": "1.1.1", 15 | "ieee754": "1.1.13", 16 | "jmespath": "0.15.0", 17 | "querystring": "0.2.0", 18 | "sax": "1.2.1", 19 | "url": "0.10.3", 20 | "uuid": "3.3.2", 21 | "xml2js": "0.4.19" 22 | }, 23 | "dependencies": { 24 | "uuid": { 25 | "version": "3.3.2", 26 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 27 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 28 | "dev": true 29 | } 30 | } 31 | }, 32 | "base64-js": { 33 | "version": "1.5.1", 34 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 35 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 36 | "dev": true 37 | }, 38 | "buffer": { 39 | "version": "4.9.2", 40 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 41 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 42 | "dev": true, 43 | "requires": { 44 | "base64-js": "^1.0.2", 45 | "ieee754": "^1.1.4", 46 | "isarray": "^1.0.0" 47 | } 48 | }, 49 | "events": { 50 | "version": "1.1.1", 51 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 52 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 53 | "dev": true 54 | }, 55 | "ieee754": { 56 | "version": "1.1.13", 57 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 58 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 59 | "dev": true 60 | }, 61 | "isarray": { 62 | "version": "1.0.0", 63 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 64 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 65 | "dev": true 66 | }, 67 | "jmespath": { 68 | "version": "0.15.0", 69 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 70 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 71 | "dev": true 72 | }, 73 | "punycode": { 74 | "version": "1.3.2", 75 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 76 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 77 | "dev": true 78 | }, 79 | "querystring": { 80 | "version": "0.2.0", 81 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 82 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 83 | "dev": true 84 | }, 85 | "sax": { 86 | "version": "1.2.1", 87 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 88 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 89 | "dev": true 90 | }, 91 | "url": { 92 | "version": "0.10.3", 93 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 94 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 95 | "dev": true, 96 | "requires": { 97 | "punycode": "1.3.2", 98 | "querystring": "0.2.0" 99 | } 100 | }, 101 | "uuid": { 102 | "version": "8.3.2", 103 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 104 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 105 | }, 106 | "xml2js": { 107 | "version": "0.4.19", 108 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 109 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 110 | "dev": true, 111 | "requires": { 112 | "sax": ">=0.6.0", 113 | "xmlbuilder": "~9.0.1" 114 | } 115 | }, 116 | "xmlbuilder": { 117 | "version": "9.0.7", 118 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 119 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 120 | "dev": true 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /api/create-booking/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-booking", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "aws-sdk": "^2.817.0" 14 | }, 15 | "dependencies": { 16 | "uuid": "^8.3.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /api/list-bookings/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); 4 | AWS.config.update({ 5 | region: process.env.AWS_REGION, 6 | }); 7 | const documentClient = new AWS.DynamoDB.DocumentClient(); 8 | 9 | module.exports.list = async event => { 10 | const body = JSON.parse(event.body); 11 | 12 | if (event.requestContext.authorizer.role === 'ADMIN') { 13 | const data = await documentClient.scan({ 14 | TableName: process.env.DYNAMODB_BOOKINGS, 15 | }).promise(); 16 | 17 | return { 18 | statusCode: 200, 19 | body: JSON.stringify(data.Items), 20 | }; 21 | } 22 | 23 | return { 24 | statusCode: 403, 25 | body: JSON.stringify({ 26 | message: 'Only administrators are allowed to access this resource' 27 | }), 28 | }; 29 | }; -------------------------------------------------------------------------------- /api/list-bookings/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list-bookings", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.817.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.817.0.tgz", 10 | "integrity": "sha512-DZIdWpkcqbqsCz0MEskHsyFaqc6Tk9XIFqXAg1AKHbOgC8nU45bz+Y2osX77pU01JkS/G7OhGtGmlKDrOPvFwg==", 11 | "dev": true, 12 | "requires": { 13 | "buffer": "4.9.2", 14 | "events": "1.1.1", 15 | "ieee754": "1.1.13", 16 | "jmespath": "0.15.0", 17 | "querystring": "0.2.0", 18 | "sax": "1.2.1", 19 | "url": "0.10.3", 20 | "uuid": "3.3.2", 21 | "xml2js": "0.4.19" 22 | } 23 | }, 24 | "base64-js": { 25 | "version": "1.5.1", 26 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 27 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 28 | "dev": true 29 | }, 30 | "buffer": { 31 | "version": "4.9.2", 32 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 33 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 34 | "dev": true, 35 | "requires": { 36 | "base64-js": "^1.0.2", 37 | "ieee754": "^1.1.4", 38 | "isarray": "^1.0.0" 39 | } 40 | }, 41 | "events": { 42 | "version": "1.1.1", 43 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 44 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 45 | "dev": true 46 | }, 47 | "ieee754": { 48 | "version": "1.1.13", 49 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 50 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 51 | "dev": true 52 | }, 53 | "isarray": { 54 | "version": "1.0.0", 55 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 56 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 57 | "dev": true 58 | }, 59 | "jmespath": { 60 | "version": "0.15.0", 61 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 62 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 63 | "dev": true 64 | }, 65 | "punycode": { 66 | "version": "1.3.2", 67 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 68 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 69 | "dev": true 70 | }, 71 | "querystring": { 72 | "version": "0.2.0", 73 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 74 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 75 | "dev": true 76 | }, 77 | "sax": { 78 | "version": "1.2.1", 79 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 80 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 81 | "dev": true 82 | }, 83 | "url": { 84 | "version": "0.10.3", 85 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 86 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 87 | "dev": true, 88 | "requires": { 89 | "punycode": "1.3.2", 90 | "querystring": "0.2.0" 91 | } 92 | }, 93 | "uuid": { 94 | "version": "3.3.2", 95 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 96 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 97 | "dev": true 98 | }, 99 | "xml2js": { 100 | "version": "0.4.19", 101 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 102 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 103 | "dev": true, 104 | "requires": { 105 | "sax": ">=0.6.0", 106 | "xmlbuilder": "~9.0.1" 107 | } 108 | }, 109 | "xmlbuilder": { 110 | "version": "9.0.7", 111 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 112 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 113 | "dev": true 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /api/list-bookings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list-bookings", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "aws-sdk": "^2.817.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /api/login/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); 4 | AWS.config.update({ 5 | region: process.env.AWS_REGION, 6 | }); 7 | const documentClient = new AWS.DynamoDB.DocumentClient(); 8 | const bcrypt = require('bcryptjs'); 9 | const jwt = require('jsonwebtoken'); 10 | 11 | module.exports.login = async event => { 12 | const body = JSON.parse(event.body); 13 | 14 | const params = { 15 | TableName: process.env.DYNAMODB_USERS, 16 | IndexName: process.env.EMAIL_GSI, 17 | KeyConditionExpression: 'email = :email', 18 | ExpressionAttributeValues: { 19 | ':email': body.email, 20 | }, 21 | }; 22 | 23 | const data = await documentClient.query(params).promise(); 24 | const user = data.Items[0]; 25 | 26 | if (user) { 27 | if (bcrypt.compareSync(body.password, user.password)) { 28 | delete user.password; 29 | 30 | return { 31 | statusCode: 200, 32 | body: JSON.stringify({ token: jwt.sign(user, process.env.JWT_SECRET) }), 33 | }; 34 | } 35 | 36 | return { 37 | statusCode: 401, 38 | body: JSON.stringify({ message: 'The user credentials are incorrect' }), 39 | }; 40 | } 41 | 42 | return { 43 | statusCode: 401, 44 | body: JSON.stringify({ message: 'The user credentials are incorrect' }), 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /api/login/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.817.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.817.0.tgz", 10 | "integrity": "sha512-DZIdWpkcqbqsCz0MEskHsyFaqc6Tk9XIFqXAg1AKHbOgC8nU45bz+Y2osX77pU01JkS/G7OhGtGmlKDrOPvFwg==", 11 | "dev": true, 12 | "requires": { 13 | "buffer": "4.9.2", 14 | "events": "1.1.1", 15 | "ieee754": "1.1.13", 16 | "jmespath": "0.15.0", 17 | "querystring": "0.2.0", 18 | "sax": "1.2.1", 19 | "url": "0.10.3", 20 | "uuid": "3.3.2", 21 | "xml2js": "0.4.19" 22 | } 23 | }, 24 | "base64-js": { 25 | "version": "1.5.1", 26 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 27 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 28 | "dev": true 29 | }, 30 | "bcryptjs": { 31 | "version": "2.4.3", 32 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 33 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 34 | }, 35 | "buffer": { 36 | "version": "4.9.2", 37 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 38 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 39 | "dev": true, 40 | "requires": { 41 | "base64-js": "^1.0.2", 42 | "ieee754": "^1.1.4", 43 | "isarray": "^1.0.0" 44 | } 45 | }, 46 | "buffer-equal-constant-time": { 47 | "version": "1.0.1", 48 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 49 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 50 | }, 51 | "ecdsa-sig-formatter": { 52 | "version": "1.0.11", 53 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 54 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 55 | "requires": { 56 | "safe-buffer": "^5.0.1" 57 | } 58 | }, 59 | "events": { 60 | "version": "1.1.1", 61 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 62 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 63 | "dev": true 64 | }, 65 | "ieee754": { 66 | "version": "1.1.13", 67 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 68 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 69 | "dev": true 70 | }, 71 | "isarray": { 72 | "version": "1.0.0", 73 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 74 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 75 | "dev": true 76 | }, 77 | "jmespath": { 78 | "version": "0.15.0", 79 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 80 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 81 | "dev": true 82 | }, 83 | "jsonwebtoken": { 84 | "version": "8.5.1", 85 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 86 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 87 | "requires": { 88 | "jws": "^3.2.2", 89 | "lodash.includes": "^4.3.0", 90 | "lodash.isboolean": "^3.0.3", 91 | "lodash.isinteger": "^4.0.4", 92 | "lodash.isnumber": "^3.0.3", 93 | "lodash.isplainobject": "^4.0.6", 94 | "lodash.isstring": "^4.0.1", 95 | "lodash.once": "^4.0.0", 96 | "ms": "^2.1.1", 97 | "semver": "^5.6.0" 98 | } 99 | }, 100 | "jwa": { 101 | "version": "1.4.1", 102 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 103 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 104 | "requires": { 105 | "buffer-equal-constant-time": "1.0.1", 106 | "ecdsa-sig-formatter": "1.0.11", 107 | "safe-buffer": "^5.0.1" 108 | } 109 | }, 110 | "jws": { 111 | "version": "3.2.2", 112 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 113 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 114 | "requires": { 115 | "jwa": "^1.4.1", 116 | "safe-buffer": "^5.0.1" 117 | } 118 | }, 119 | "lodash.includes": { 120 | "version": "4.3.0", 121 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 122 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 123 | }, 124 | "lodash.isboolean": { 125 | "version": "3.0.3", 126 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 127 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 128 | }, 129 | "lodash.isinteger": { 130 | "version": "4.0.4", 131 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 132 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 133 | }, 134 | "lodash.isnumber": { 135 | "version": "3.0.3", 136 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 137 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 138 | }, 139 | "lodash.isplainobject": { 140 | "version": "4.0.6", 141 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 142 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 143 | }, 144 | "lodash.isstring": { 145 | "version": "4.0.1", 146 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 147 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 148 | }, 149 | "lodash.once": { 150 | "version": "4.1.1", 151 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 152 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 153 | }, 154 | "ms": { 155 | "version": "2.1.3", 156 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 157 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 158 | }, 159 | "punycode": { 160 | "version": "1.3.2", 161 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 162 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 163 | "dev": true 164 | }, 165 | "querystring": { 166 | "version": "0.2.0", 167 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 168 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 169 | "dev": true 170 | }, 171 | "safe-buffer": { 172 | "version": "5.2.1", 173 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 174 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 175 | }, 176 | "sax": { 177 | "version": "1.2.1", 178 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 179 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 180 | "dev": true 181 | }, 182 | "semver": { 183 | "version": "5.7.1", 184 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 185 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 186 | }, 187 | "url": { 188 | "version": "0.10.3", 189 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 190 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 191 | "dev": true, 192 | "requires": { 193 | "punycode": "1.3.2", 194 | "querystring": "0.2.0" 195 | } 196 | }, 197 | "uuid": { 198 | "version": "3.3.2", 199 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 200 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 201 | "dev": true 202 | }, 203 | "xml2js": { 204 | "version": "0.4.19", 205 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 206 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 207 | "dev": true, 208 | "requires": { 209 | "sax": ">=0.6.0", 210 | "xmlbuilder": "~9.0.1" 211 | } 212 | }, 213 | "xmlbuilder": { 214 | "version": "9.0.7", 215 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 216 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 217 | "dev": true 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /api/login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "aws-sdk": "^2.817.0" 14 | }, 15 | "dependencies": { 16 | "bcryptjs": "^2.4.3", 17 | "jsonwebtoken": "^8.5.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/register/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); 4 | AWS.config.update({ 5 | region: process.env.AWS_REGION, 6 | }); 7 | const documentClient = new AWS.DynamoDB.DocumentClient(); 8 | const bcrypt = require('bcryptjs'); 9 | const { v4: uuidv4 } = require('uuid'); 10 | 11 | module.exports.register = async event => { 12 | const body = JSON.parse(event.body); 13 | 14 | await documentClient.put({ 15 | TableName: process.env.DYNAMODB_USERS, 16 | Item: { 17 | id: uuidv4(), 18 | name: body.name, 19 | email: body.email, 20 | password: bcrypt.hashSync(body.password, 10), 21 | }, 22 | }).promise(); 23 | 24 | return { 25 | statusCode: 201, 26 | body: JSON.stringify({ message: 'The user was registered successfully' }), 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /api/register/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.817.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.817.0.tgz", 10 | "integrity": "sha512-DZIdWpkcqbqsCz0MEskHsyFaqc6Tk9XIFqXAg1AKHbOgC8nU45bz+Y2osX77pU01JkS/G7OhGtGmlKDrOPvFwg==", 11 | "dev": true, 12 | "requires": { 13 | "buffer": "4.9.2", 14 | "events": "1.1.1", 15 | "ieee754": "1.1.13", 16 | "jmespath": "0.15.0", 17 | "querystring": "0.2.0", 18 | "sax": "1.2.1", 19 | "url": "0.10.3", 20 | "uuid": "3.3.2", 21 | "xml2js": "0.4.19" 22 | }, 23 | "dependencies": { 24 | "uuid": { 25 | "version": "3.3.2", 26 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 27 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 28 | "dev": true 29 | } 30 | } 31 | }, 32 | "base64-js": { 33 | "version": "1.5.1", 34 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 35 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 36 | "dev": true 37 | }, 38 | "bcryptjs": { 39 | "version": "2.4.3", 40 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 41 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 42 | }, 43 | "buffer": { 44 | "version": "4.9.2", 45 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 46 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 47 | "dev": true, 48 | "requires": { 49 | "base64-js": "^1.0.2", 50 | "ieee754": "^1.1.4", 51 | "isarray": "^1.0.0" 52 | } 53 | }, 54 | "events": { 55 | "version": "1.1.1", 56 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 57 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 58 | "dev": true 59 | }, 60 | "ieee754": { 61 | "version": "1.1.13", 62 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 63 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 64 | "dev": true 65 | }, 66 | "isarray": { 67 | "version": "1.0.0", 68 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 69 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 70 | "dev": true 71 | }, 72 | "jmespath": { 73 | "version": "0.15.0", 74 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 75 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 76 | "dev": true 77 | }, 78 | "punycode": { 79 | "version": "1.3.2", 80 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 81 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 82 | "dev": true 83 | }, 84 | "querystring": { 85 | "version": "0.2.0", 86 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 87 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 88 | "dev": true 89 | }, 90 | "sax": { 91 | "version": "1.2.1", 92 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 93 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 94 | "dev": true 95 | }, 96 | "url": { 97 | "version": "0.10.3", 98 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 99 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 100 | "dev": true, 101 | "requires": { 102 | "punycode": "1.3.2", 103 | "querystring": "0.2.0" 104 | } 105 | }, 106 | "uuid": { 107 | "version": "8.3.2", 108 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 109 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 110 | }, 111 | "xml2js": { 112 | "version": "0.4.19", 113 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 114 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 115 | "dev": true, 116 | "requires": { 117 | "sax": ">=0.6.0", 118 | "xmlbuilder": "~9.0.1" 119 | } 120 | }, 121 | "xmlbuilder": { 122 | "version": "9.0.7", 123 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 124 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 125 | "dev": true 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /api/register/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "uuid": "^8.3.2" 15 | }, 16 | "devDependencies": { 17 | "aws-sdk": "^2.817.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/serverless.yml: -------------------------------------------------------------------------------- 1 | service: api 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs12.x 8 | 9 | stage: dev 10 | region: us-east-1 11 | 12 | functions: 13 | register: 14 | role: ${ssm:${self:custom.stage}-register-iam-role} 15 | handler: register/handler.register 16 | environment: 17 | DYNAMODB_USERS: ${ssm:${self:custom.stage}-dynamodb-users-table} 18 | events: 19 | - http: 20 | path: users 21 | method: post 22 | login: 23 | role: ${ssm:${self:custom.stage}-login-iam-role} 24 | handler: login/handler.login 25 | environment: 26 | DYNAMODB_USERS: ${ssm:${self:custom.stage}-dynamodb-users-table} 27 | JWT_SECRET: ${ssm:${self:custom.stage}-jwt-secret} 28 | EMAIL_GSI: ${ssm:${self:custom.stage}-email-gsi} 29 | events: 30 | - http: 31 | path: login 32 | method: post 33 | create_booking: 34 | role: ${ssm:${self:custom.stage}-create-booking-iam-role} 35 | handler: create-booking/handler.create 36 | environment: 37 | DYNAMODB_BOOKINGS: ${ssm:${self:custom.stage}-dynamodb-bookings-table} 38 | events: 39 | - http: 40 | path: bookings 41 | method: post 42 | authorizer: authorizer 43 | list_bookings: 44 | role: ${ssm:${self:custom.stage}-list-bookings-iam-role} 45 | handler: list-bookings/handler.list 46 | environment: 47 | DYNAMODB_BOOKINGS: ${ssm:${self:custom.stage}-dynamodb-bookings-table} 48 | events: 49 | - http: 50 | path: bookings 51 | method: get 52 | authorizer: authorizer 53 | authorizer: 54 | handler: authorizer/handler.authorizer 55 | environment: 56 | JWT_SECRET: ${ssm:${self:custom.stage}-jwt-secret} 57 | 58 | custom: 59 | stage: ${opt:stage, self:provider.stage} -------------------------------------------------------------------------------- /assets/logo-terraform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eduardo3g/terraform-serverless/70d093a97e50ff23f61aedd68cb6704ac98f482d/assets/logo-terraform.png -------------------------------------------------------------------------------- /bookings-consumer/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /bookings-consumer/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk'); 4 | AWS.config.update({ 5 | region: process.env.AWS_REGION, 6 | }); 7 | const SNS = new AWS.SNS(); 8 | const converter = AWS.DynamoDB.Converter; 9 | const moment = require('moment'); 10 | 11 | moment.locale('pt-br'); 12 | 13 | module.exports.listen = async event => { 14 | const snsPromises = []; 15 | 16 | for (let record of event.Records) { 17 | if (record.eventName === 'INSERT') { 18 | const booking = converter.unmarshall(record.dynamodb.NewImage); 19 | 20 | snsPromises.push(SNS.publish({ 21 | TopicArn: process.env.SNS_NOTIFICATIONS_TOPIC, 22 | Message: `Booking created: the user ${booking.user.name} (${booking.user.email}) created a booking on: ${moment(booking.date).format('LLLL')}`, 23 | }).promise()); 24 | } 25 | } 26 | 27 | await Promise.all(snsPromises); 28 | 29 | console.log('Message(s) sent successfully to SNS topic'); 30 | 31 | return { message: 'Go Serverless v1.0! Your function executed successfully!' }; 32 | }; 33 | -------------------------------------------------------------------------------- /bookings-consumer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookings-consumer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.817.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.817.0.tgz", 10 | "integrity": "sha512-DZIdWpkcqbqsCz0MEskHsyFaqc6Tk9XIFqXAg1AKHbOgC8nU45bz+Y2osX77pU01JkS/G7OhGtGmlKDrOPvFwg==", 11 | "dev": true, 12 | "requires": { 13 | "buffer": "4.9.2", 14 | "events": "1.1.1", 15 | "ieee754": "1.1.13", 16 | "jmespath": "0.15.0", 17 | "querystring": "0.2.0", 18 | "sax": "1.2.1", 19 | "url": "0.10.3", 20 | "uuid": "3.3.2", 21 | "xml2js": "0.4.19" 22 | } 23 | }, 24 | "base64-js": { 25 | "version": "1.5.1", 26 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 27 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 28 | "dev": true 29 | }, 30 | "buffer": { 31 | "version": "4.9.2", 32 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 33 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 34 | "dev": true, 35 | "requires": { 36 | "base64-js": "^1.0.2", 37 | "ieee754": "^1.1.4", 38 | "isarray": "^1.0.0" 39 | } 40 | }, 41 | "events": { 42 | "version": "1.1.1", 43 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 44 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 45 | "dev": true 46 | }, 47 | "ieee754": { 48 | "version": "1.1.13", 49 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 50 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 51 | "dev": true 52 | }, 53 | "isarray": { 54 | "version": "1.0.0", 55 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 56 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 57 | "dev": true 58 | }, 59 | "jmespath": { 60 | "version": "0.15.0", 61 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 62 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 63 | "dev": true 64 | }, 65 | "moment": { 66 | "version": "2.29.1", 67 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 68 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" 69 | }, 70 | "punycode": { 71 | "version": "1.3.2", 72 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 73 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 74 | "dev": true 75 | }, 76 | "querystring": { 77 | "version": "0.2.0", 78 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 79 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 80 | "dev": true 81 | }, 82 | "sax": { 83 | "version": "1.2.1", 84 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 85 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", 86 | "dev": true 87 | }, 88 | "url": { 89 | "version": "0.10.3", 90 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 91 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 92 | "dev": true, 93 | "requires": { 94 | "punycode": "1.3.2", 95 | "querystring": "0.2.0" 96 | } 97 | }, 98 | "uuid": { 99 | "version": "3.3.2", 100 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 101 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 102 | "dev": true 103 | }, 104 | "xml2js": { 105 | "version": "0.4.19", 106 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 107 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 108 | "dev": true, 109 | "requires": { 110 | "sax": ">=0.6.0", 111 | "xmlbuilder": "~9.0.1" 112 | } 113 | }, 114 | "xmlbuilder": { 115 | "version": "9.0.7", 116 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 117 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 118 | "dev": true 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /bookings-consumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookings-consumer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "aws-sdk": "^2.817.0" 14 | }, 15 | "dependencies": { 16 | "moment": "^2.29.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bookings-consumer/serverless.yml: -------------------------------------------------------------------------------- 1 | service: bookings-consumer 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs12.x 8 | 9 | stage: dev 10 | region: us-east-1 11 | 12 | functions: 13 | stream_listener: 14 | handler: handler.listen 15 | role: ${ssm:${self:custom.stage}-bookings-stream-consumer-iam-role} 16 | events: 17 | - stream: ${ssm:${self:custom.stage}-dynamodb-bookings-stream} 18 | environment: 19 | SNS_NOTIFICATIONS_TOPIC: ${ssm:${self:custom.stage}-notifications-topic} 20 | 21 | custom: 22 | stage: ${opt:stage, self:provider.stage} -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | cd terraform/environments/$1 2 | 3 | terraform init && terraform apply -auto-approve 4 | 5 | cd ../../../ 6 | pwd 7 | 8 | executeSls () { 9 | pwd 10 | sls deploy --stage $1 11 | } 12 | 13 | cd api/ 14 | executeSls $1 15 | cd ../bookings-consumer/ 16 | executeSls $1 17 | cd ../sms-notification/ 18 | executeSls $1 19 | cd ../email-notification 20 | executeSls $1 -------------------------------------------------------------------------------- /destroy.sh: -------------------------------------------------------------------------------- 1 | executeSls () { 2 | pwd 3 | sls remove --stage $1 4 | } 5 | 6 | cd api/ 7 | executeSls $1 8 | cd ../bookings-consumer/ 9 | executeSls $1 10 | cd ../sms-notification/ 11 | executeSls $1 12 | cd ../email-notification 13 | executeSls $1 14 | 15 | cd ../terraform/environments/$1 16 | pwd 17 | terraform destroy -auto-approve -------------------------------------------------------------------------------- /email-notification/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /email-notification/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nodemailer = require('nodemailer'); 4 | 5 | const transporter = nodemailer.createTransport({ 6 | host: process.env.SMTP_SERVER, 7 | port: 465, 8 | secure: true, 9 | auth: { 10 | user: process.env.EMAIL_FROM, 11 | pass: process.env.EMAIL_FROM_PASSWORD, 12 | }, 13 | }); 14 | 15 | module.exports.send = async event => { 16 | let emailPromises = []; 17 | 18 | for (let record of event.Records) { 19 | const message = JSON.parse(record.body).Message; 20 | 21 | emailPromises.push(transporter.sendMail({ 22 | from: `"Reservations 👻" <${process.env.EMAIL_FROM}>`, 23 | to: process.env.EMAIL_TO, 24 | subject: "Reservation confirmed ✔", 25 | text: message, 26 | html: message, 27 | })); 28 | } 29 | 30 | await Promise.all(emailPromises); 31 | 32 | console.log('All the email were sent successfully'); 33 | 34 | return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; 35 | }; 36 | -------------------------------------------------------------------------------- /email-notification/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-notification", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "nodemailer": { 8 | "version": "6.4.17", 9 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz", 10 | "integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /email-notification/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-notification", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "nodemailer": "^6.4.17" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /email-notification/serverless.yml: -------------------------------------------------------------------------------- 1 | service: email-notification 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs12.x 8 | 9 | stage: dev 10 | region: us-east-1 11 | 12 | functions: 13 | send_email: 14 | handler: handler.send 15 | role: ${ssm:${self:custom.stage}-email-iam-role} 16 | events: 17 | - sqs: ${ssm:${self:custom.stage}-email-sqs} 18 | environment: 19 | SMTP_SERVER: ${ssm:${self:custom.stage}-smtp-server} 20 | EMAIL_FROM: ${ssm:${self:custom.stage}-email-from} 21 | EMAIL_FROM_PASSWORD: ${ssm:${self:custom.stage}-email-from-password} 22 | EMAIL_TO: ${ssm:${self:custom.stage}-email-to} 23 | 24 | custom: 25 | stage: ${opt:stage, self:provider.stage} -------------------------------------------------------------------------------- /sms-notification/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /sms-notification/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const messagebird = require('messagebird')(process.env.MESSAGE_BIRD_API_KEY); 4 | 5 | module.exports.send = async event => { 6 | const smsPromises = []; 7 | 8 | for (let record of event.Records) { 9 | const message = JSON.parse(record.body).Message; 10 | 11 | smsPromises.push(sendMessagePromise(message)) 12 | } 13 | 14 | await Promise.all(smsPromises); 15 | 16 | console.log('SMSs sent successfully'); 17 | 18 | return { 19 | message: 'Go Serverless v1.0! Your function executed successfully!', 20 | event, 21 | }; 22 | }; 23 | 24 | const sendMessagePromise = message => { 25 | return new Promise((res, rej) => { 26 | messagebird.messages.create({ 27 | originator: process.env.SMS_PHONE_FROM, 28 | recipients: [process.env.SMS_PHONE_TO], 29 | body: message, 30 | }, (err, callback) => { 31 | if (!err) { 32 | console.log(JSON.stringify(callback)); 33 | return res({}); 34 | } 35 | return (rej(err)); 36 | }); 37 | }); 38 | }; -------------------------------------------------------------------------------- /sms-notification/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sms-notification", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "messagebird": { 8 | "version": "3.5.0", 9 | "resolved": "https://registry.npmjs.org/messagebird/-/messagebird-3.5.0.tgz", 10 | "integrity": "sha512-LNzMPr6PuZBHFjDdrAQR5aRad2QHv4C4BcumySyE3DKvsd0k2p8oYX2mFHzk8uMO5KstDJVe6Yz2/bNDADo4SQ==", 11 | "requires": { 12 | "safe-buffer": "^5.1.2", 13 | "scmp": "^2.0.0" 14 | } 15 | }, 16 | "safe-buffer": { 17 | "version": "5.2.1", 18 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 19 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 20 | }, 21 | "scmp": { 22 | "version": "2.1.0", 23 | "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", 24 | "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sms-notification/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sms-notification", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "messagebird": "^3.5.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sms-notification/serverless.yml: -------------------------------------------------------------------------------- 1 | service: sms-notification 2 | 3 | frameworkVersion: '2' 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs12.x 8 | 9 | stage: dev 10 | region: us-east-1 11 | 12 | functions: 13 | send_sms: 14 | memorySize: ${self:custom.memorySize.${self:custom.stage}} 15 | handler: handler.send 16 | role: ${ssm:${self:custom.stage}-sms-iam-role} 17 | events: 18 | - sqs: ${ssm:${self:custom.stage}-sms-sqs} 19 | environment: 20 | MESSAGE_BIRD_API_KEY: ${ssm:${self:custom.stage}-message-bird-api-key} 21 | SMS_PHONE_FROM: ${ssm:${self:custom.stage}-sms-phone-from} 22 | SMS_PHONE_TO: ${ssm:${self:custom.stage}-sms-phone-to} 23 | 24 | custom: 25 | stage: ${opt:stage, self:provider.stage} 26 | memorySize: 27 | dev: 128 28 | prod: 2048 -------------------------------------------------------------------------------- /terraform/environments/dev/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | module "users" { 4 | source = "../../infra/users" 5 | environment = var.environment 6 | write_capacity = 1 7 | read_capacity = 1 8 | jwt_secret = var.jwt_secret 9 | admin_id = var.admin_id 10 | admin_name = var.admin_name 11 | admin_password = var.admin_password 12 | admin_email = var.admin_email 13 | } 14 | 15 | module "bookings" { 16 | source = "../../infra/bookings" 17 | environment = var.environment 18 | write_capacity = 1 19 | read_capacity = 1 20 | sns_notifications_arn = module.notifications.notifications_topic_arn 21 | } 22 | 23 | module "notifications" { 24 | source = "../../infra/notifications" 25 | environment = var.environment 26 | account_id = data.aws_caller_identity.current.account_id 27 | region = var.region 28 | } 29 | 30 | module "system" { 31 | source = "../../infra/system" 32 | environment = var.environment 33 | email_from = var.email_from 34 | email_from_password = var.email_from_password 35 | email_to = var.email_to 36 | smtp_server = var.smtp_server 37 | message_bird_api_key = var.message_bird_api_key 38 | sms_phone_from = var.sms_phone_from 39 | sms_phone_to = var.sms_phone_to 40 | } -------------------------------------------------------------------------------- /terraform/environments/dev/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } -------------------------------------------------------------------------------- /terraform/environments/dev/secrets.auto.tfvars: -------------------------------------------------------------------------------- 1 | jwt_secret = "0YfU3LDpD-7f9hhtY1rmhmTdqCrmoeHRzARBlOjZqME" 2 | admin_id = "d1ad9c81-ed4f-41bc-82a1-d7d966706b52" 3 | admin_email = "admin@email.com" 4 | admin_password = "$2a$10$..k73TdFFOpbIjYqk8AI5eTZYj0ulyq/QyYD22ij3.kEeJ5Mu5OMu" #admin@123 5 | admin_name = "Admin" 6 | email_from = "yourmail@zohomail.com" 7 | email_from_password = "yousecretzohopassword" 8 | email_to = "joxil80150@mmgaklan.com" 9 | smtp_server = "smtp.zoho.com" 10 | message_bird_api_key = "test_messagebird_apikey" 11 | sms_phone_from = "+5511900000000" 12 | sms_phone_to = "+5511900000000" 13 | -------------------------------------------------------------------------------- /terraform/environments/dev/variables.auto.tfvars: -------------------------------------------------------------------------------- 1 | environment = "dev" 2 | region = "us-east-1" -------------------------------------------------------------------------------- /terraform/environments/dev/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | 3 | } 4 | 5 | variable "jwt_secret" { 6 | 7 | } 8 | 9 | variable "admin_id" { 10 | 11 | } 12 | 13 | variable "admin_email" { 14 | 15 | } 16 | 17 | variable "admin_password" { 18 | 19 | } 20 | 21 | variable "admin_name" { 22 | 23 | } 24 | 25 | variable "region" { 26 | 27 | } 28 | 29 | variable "email_from" { 30 | 31 | } 32 | 33 | variable "email_from_password" { 34 | 35 | } 36 | 37 | variable "email_to" { 38 | 39 | } 40 | 41 | variable "smtp_server" { 42 | 43 | } 44 | 45 | variable "message_bird_api_key" { 46 | 47 | } 48 | 49 | variable "sms_phone_from" { 50 | 51 | } 52 | 53 | variable "sms_phone_to" { 54 | 55 | } -------------------------------------------------------------------------------- /terraform/environments/prod/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | module "users" { 4 | source = "../../infra/users" 5 | environment = var.environment 6 | write_capacity = 10 7 | read_capacity = 10 8 | jwt_secret = var.jwt_secret 9 | admin_id = var.admin_id 10 | admin_name = var.admin_name 11 | admin_password = var.admin_password 12 | admin_email = var.admin_email 13 | } 14 | 15 | module "bookings" { 16 | source = "../../infra/bookings" 17 | environment = var.environment 18 | write_capacity = 1 19 | read_capacity = 1 20 | sns_notifications_arn = module.notifications.notifications_topic_arn 21 | } 22 | 23 | module "notifications" { 24 | source = "../../infra/notifications" 25 | environment = var.environment 26 | account_id = data.aws_caller_identity.current.account_id 27 | region = var.region 28 | } 29 | 30 | module "system" { 31 | source = "../../infra/system" 32 | environment = var.environment 33 | email_from = var.email_from 34 | email_from_password = var.email_from_password 35 | email_to = var.email_to 36 | smtp_server = var.smtp_server 37 | message_bird_api_key = var.message_bird_api_key 38 | sms_phone_from = var.sms_phone_from 39 | sms_phone_to = var.sms_phone_to 40 | } -------------------------------------------------------------------------------- /terraform/environments/prod/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } -------------------------------------------------------------------------------- /terraform/environments/prod/secrets.auto.tfvars: -------------------------------------------------------------------------------- 1 | jwt_secret = "f4f8f3BkbRGdsRsRWimxvfS5OJ5hibkTxH5yESDOR3c=" 2 | admin_id = "d1ad9c81-ed4f-41bc-82a1-da73faw706b52" 3 | admin_email = "admin@email.com" 4 | admin_password = "$2a$10$brvPOhBESQrOO96VWdQSI.ek3BDslJ9tfiJHhEfGpmxMygYBxxVsq" #adminprod@123 5 | admin_name = "Admin Production" 6 | email_from = "yourmail@zohomail.com" 7 | email_from_password = "mysuperstrongpassword" 8 | email_to = "lacemi1426@nowdigit.com" 9 | smtp_server = "smtp.zoho.com" 10 | message_bird_api_key = "prod_messagebird_apikey" 11 | sms_phone_from = "+5511900000000" 12 | sms_phone_to = "+5511900000000" 13 | -------------------------------------------------------------------------------- /terraform/environments/prod/variables.auto.tfvars: -------------------------------------------------------------------------------- 1 | environment = "prod" 2 | region = "us-east-1" -------------------------------------------------------------------------------- /terraform/environments/prod/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | 3 | } 4 | 5 | variable "jwt_secret" { 6 | 7 | } 8 | 9 | variable "admin_id" { 10 | 11 | } 12 | 13 | variable "admin_email" { 14 | 15 | } 16 | 17 | variable "admin_password" { 18 | 19 | } 20 | 21 | variable "admin_name" { 22 | 23 | } 24 | 25 | variable "region" { 26 | 27 | } 28 | 29 | variable "email_from" { 30 | 31 | } 32 | 33 | variable "email_from_password" { 34 | 35 | } 36 | 37 | variable "email_to" { 38 | 39 | } 40 | 41 | variable "smtp_server" { 42 | 43 | } 44 | 45 | variable "message_bird_api_key" { 46 | 47 | } 48 | 49 | variable "sms_phone_from" { 50 | 51 | } 52 | 53 | variable "sms_phone_to" { 54 | 55 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/dynamodb-bookings.tf: -------------------------------------------------------------------------------- 1 | resource "aws_dynamodb_table" "bookings" { 2 | name = "${var.environment}-bookings" 3 | hash_key = "id" 4 | attribute { 5 | name = "id" 6 | type = "S" 7 | } 8 | write_capacity = var.write_capacity 9 | read_capacity = var.read_capacity 10 | stream_enabled = true 11 | stream_view_type = "NEW_IMAGE" 12 | } 13 | 14 | resource "aws_ssm_parameter" "dynamodb_bookings_table" { 15 | name = "${var.environment}-dynamodb-bookings-table" 16 | type = "String" 17 | value = aws_dynamodb_table.bookings.name 18 | } 19 | 20 | resource "aws_ssm_parameter" "dynamodb_bookings_stream" { 21 | name = "${var.environment}-dynamodb-bookings-stream" 22 | type = "String" 23 | value = aws_dynamodb_table.bookings.stream_arn 24 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-policy-attachment-bookings-stream-consumer.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy_attachment" "bookings_stream_consumer_policy_attachment" { 2 | name = "${var.environment}-bookings-stream-consumer-attachment" 3 | roles = [aws_iam_role.bookings_stream_consumer_iam_role.name] 4 | policy_arn = aws_iam_policy.bookings_stream_consumer_policy.arn 5 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-policy-attachment-create-booking.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy_attachment" "create_booking_policy_attachment" { 2 | name = "${var.environment}-create-booking-attachment" 3 | roles = [aws_iam_role.create_booking_iam_role.name] 4 | policy_arn = aws_iam_policy.create_booking_policy.arn 5 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-policy-attachment-list-bookings.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy_attachment" "list_bookings_policy_attachment" { 2 | name = "${var.environment}-list-bookings-attachment" 3 | roles = [aws_iam_role.list_bookings_iam_role.name] 4 | policy_arn = aws_iam_policy.list_bookings_policy.arn 5 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-policy-bookings-stream-consumer.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy" "bookings_stream_consumer_policy" { 2 | name = "${var.environment}-bookings-stream-consumer-policy" 3 | policy = templatefile("${path.module}/templates/dynamodb-policy.tpl", { 4 | action = join("\",\"", [ 5 | "dynamodb:DescribeStream", 6 | "dynamodb:GetRecords", 7 | "dynamodb:GetShardIterator", 8 | "dynamodb:ListStreams" 9 | ] 10 | ), 11 | resource = aws_dynamodb_table.bookings.stream_arn 12 | sns_topic = var.sns_notifications_arn 13 | }) 14 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-policy-create-booking.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy" "create_booking_policy" { 2 | name = "${var.environment}-create-booking-policy" 3 | policy = templatefile("${path.module}/templates/dynamodb-policy.tpl", { 4 | action = "dynamodb:PutItem", 5 | resource = aws_dynamodb_table.bookings.arn, 6 | sns_topic = "" 7 | }) 8 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-policy-list-bookings.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy" "list_bookings_policy" { 2 | name = "${var.environment}-list-bookings-policy" 3 | policy = templatefile("${path.module}/templates/dynamodb-policy.tpl", { 4 | action = "dynamodb:Scan", 5 | resource = aws_dynamodb_table.bookings.arn, 6 | sns_topic = "" 7 | }) 8 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-role-bookings-stream-consumer.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "bookings_stream_consumer_iam_role" { 2 | name = "${var.environment}-bookings-stream-consumer-iam-role" 3 | 4 | assume_role_policy = templatefile("${path.module}/templates/lambda-base-policy.tpl", {}) 5 | } 6 | 7 | resource "aws_ssm_parameter" "bookings_stream_consumer_iam_role" { 8 | name = "${var.environment}-bookings-stream-consumer-iam-role" 9 | type = "String" 10 | value = aws_iam_role.bookings_stream_consumer_iam_role.arn 11 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-role-create-booking.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "create_booking_iam_role" { 2 | name = "${var.environment}-create-booking-iam-role" 3 | 4 | assume_role_policy = templatefile("${path.module}/templates/lambda-base-policy.tpl", {}) 5 | } 6 | 7 | resource "aws_ssm_parameter" "create_booking_iam_role" { 8 | name = "${var.environment}-create-booking-iam-role" 9 | type = "String" 10 | value = aws_iam_role.create_booking_iam_role.arn 11 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/iam-role-list-bookings.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "list_bookings_iam_role" { 2 | name = "${var.environment}-list-bookings-iam-role" 3 | 4 | assume_role_policy = templatefile("${path.module}/templates/lambda-base-policy.tpl", {}) 5 | } 6 | 7 | resource "aws_ssm_parameter" "list-bookings_iam_role" { 8 | name = "${var.environment}-list-bookings-iam-role" 9 | type = "String" 10 | value = aws_iam_role.list_bookings_iam_role.arn 11 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/templates/dynamodb-policy.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": [ 6 | "${action}" 7 | ], 8 | "Effect": "Allow", 9 | "Resource": "${resource}" 10 | }, 11 | { 12 | "Effect": "Allow", 13 | "Action": [ 14 | "logs:CreateLogGroup", 15 | "logs:CreateLogStream", 16 | "logs:PutLogEvents" 17 | ], 18 | "Resource": "*" 19 | } 20 | %{ if sns_topic != "" } 21 | , { 22 | "Effect": "Allow", 23 | "Action": [ 24 | "sns:Publish" 25 | ], 26 | "Resource": "${sns_topic}" 27 | } 28 | %{ endif } 29 | ] 30 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/templates/lambda-base-policy.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": "sts:AssumeRole", 6 | "Principal": { 7 | "Service": "lambda.amazonaws.com" 8 | }, 9 | "Effect": "Allow", 10 | "Sid": "" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /terraform/infra/bookings/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | 3 | } 4 | 5 | variable "write_capacity" { 6 | 7 | } 8 | 9 | variable "read_capacity" { 10 | 11 | } 12 | 13 | variable "sns_notifications_arn" { 14 | 15 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/iam-policy-attachment-email.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy_attachment" "email_policy_attachment" { 2 | name = "${var.environment}-email-attachment" 3 | roles = [aws_iam_role.email_iam_role.name] 4 | policy_arn = aws_iam_policy.email_policy.arn 5 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/iam-policy-attachment-sms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy_attachment" "sms_policy_attachment" { 2 | name = "${var.environment}-sms-attachment" 3 | roles = [aws_iam_role.sms_iam_role.name] 4 | policy_arn = aws_iam_policy.sms_policy.arn 5 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/iam-policy-email.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy" "email_policy" { 2 | name = "${var.environment}-email-policy" 3 | policy = templatefile("${path.module}/templates/lambda-sqs-policy.tpl", { 4 | action = join("\",\"", [ 5 | "sqs:ReceiveMessage", 6 | "sqs:DeleteMessage", 7 | "sqs:GetQueueAttributes" 8 | ] 9 | ), 10 | resource = aws_sqs_queue.email.arn 11 | }) 12 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/iam-policy-sms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_policy" "sms_policy" { 2 | name = "${var.environment}-sms-policy" 3 | policy = templatefile("${path.module}/templates/lambda-sqs-policy.tpl", { 4 | action = join("\",\"", [ 5 | "sqs:ReceiveMessage", 6 | "sqs:DeleteMessage", 7 | "sqs:GetQueueAttributes" 8 | ] 9 | ), 10 | resource = aws_sqs_queue.sms.arn 11 | }) 12 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/iam-role-email.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "email_iam_role" { 2 | name = "${var.environment}-email-iam-role" 3 | 4 | assume_role_policy = templatefile("${path.module}/templates/lambda-base-policy.tpl", {}) 5 | } 6 | 7 | resource "aws_ssm_parameter" "email_iam_role" { 8 | name = "${var.environment}-email-iam-role" 9 | type = "String" 10 | value = aws_iam_role.email_iam_role.arn 11 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/iam-role-sms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "sms_iam_role" { 2 | name = "${var.environment}-sms-iam-role" 3 | 4 | assume_role_policy = templatefile("${path.module}/templates/lambda-base-policy.tpl", {}) 5 | } 6 | 7 | resource "aws_ssm_parameter" "sms_iam_role" { 8 | name = "${var.environment}-sms-iam-role" 9 | type = "String" 10 | value = aws_iam_role.sms_iam_role.arn 11 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/sns-subscriptions.tf: -------------------------------------------------------------------------------- 1 | resource "aws_sns_topic_subscription" "email_subscription" { 2 | topic_arn = aws_sns_topic.notifications.arn 3 | protocol = "sqs" 4 | endpoint = aws_sqs_queue.email.arn 5 | } 6 | 7 | resource "aws_sns_topic_subscription" "sms_subscription" { 8 | topic_arn = aws_sns_topic.notifications.arn 9 | protocol = "sqs" 10 | endpoint = aws_sqs_queue.sms.arn 11 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/sns.tf: -------------------------------------------------------------------------------- 1 | resource "aws_sns_topic" "notifications" { 2 | name = "${var.environment}-notifications" 3 | } 4 | 5 | resource "aws_ssm_parameter" "notifications_topic" { 6 | name = "${var.environment}-notifications-topic" 7 | type = "String" 8 | value = aws_sns_topic.notifications.arn 9 | } 10 | 11 | output "notifications_topic_arn" { 12 | value = aws_sns_topic.notifications.arn 13 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/sqs-email.tf: -------------------------------------------------------------------------------- 1 | resource "aws_sqs_queue" "email" { 2 | name = "${var.environment}-email-queue" 3 | redrive_policy = jsonencode({ 4 | deadLetterTargetArn = aws_sqs_queue.email_dlq.arn 5 | maxReceiveCount = 3 6 | }) 7 | policy = templatefile("${path.module}/templates/sqs-sns-policy.tpl", { 8 | resource = "arn:aws:sqs:${var.region}:${var.account_id}:${var.environment}-email-queue" 9 | source_arn = aws_sns_topic.notifications.arn 10 | }) 11 | } 12 | 13 | resource "aws_ssm_parameter" "email_sqs" { 14 | name = "${var.environment}-email-sqs" 15 | type = "String" 16 | value = aws_sqs_queue.email.arn 17 | } 18 | 19 | resource "aws_sqs_queue" "email_dlq" { 20 | name = "${var.environment}-email-queue-dlq" 21 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/sqs-sms.tf: -------------------------------------------------------------------------------- 1 | resource "aws_sqs_queue" "sms" { 2 | name = "${var.environment}-sms-queue" 3 | redrive_policy = jsonencode({ 4 | deadLetterTargetArn = aws_sqs_queue.sms_dlq.arn 5 | maxReceiveCount = 3 6 | }) 7 | policy = templatefile("${path.module}/templates/sqs-sns-policy.tpl", { 8 | resource = "arn:aws:sqs:${var.region}:${var.account_id}:${var.environment}-sms-queue" 9 | source_arn = aws_sns_topic.notifications.arn 10 | }) 11 | } 12 | 13 | resource "aws_ssm_parameter" "sms_sqs" { 14 | name = "${var.environment}-sms-sqs" 15 | type = "String" 16 | value = aws_sqs_queue.sms.arn 17 | } 18 | 19 | resource "aws_sqs_queue" "sms_dlq" { 20 | name = "${var.environment}-sms-queue-dlq" 21 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/templates/lambda-base-policy.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": "sts:AssumeRole", 6 | "Principal": { 7 | "Service": "lambda.amazonaws.com" 8 | }, 9 | "Effect": "Allow", 10 | "Sid": "" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/templates/lambda-sqs-policy.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": [ 6 | "${action}" 7 | ], 8 | "Effect": "Allow", 9 | "Resource": "${resource}" 10 | }, 11 | { 12 | "Effect": "Allow", 13 | "Action": [ 14 | "logs:CreateLogGroup", 15 | "logs:CreateLogStream", 16 | "logs:PutLogEvents" 17 | ], 18 | "Resource": "*" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/templates/sqs-sns-policy.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": "*", 7 | "Action": "sqs:SendMessage", 8 | "Resource": "${resource}", 9 | "Condition": { 10 | "ArnEquals": { 11 | "aws:SourceArn": "${source_arn}" 12 | } 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /terraform/infra/notifications/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | 3 | } 4 | 5 | variable "account_id" { 6 | 7 | } 8 | 9 | variable "region" { 10 | 11 | } -------------------------------------------------------------------------------- /terraform/infra/system/ssm.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ssm_parameter" "email_from" { 2 | name = "${var.environment}-email-from" 3 | type = "String" 4 | value = var.email_from 5 | } 6 | 7 | resource "aws_ssm_parameter" "email_from_password" { 8 | name = "${var.environment}-email-from-password" 9 | type = "String" 10 | value = var.email_from_password 11 | } 12 | 13 | resource "aws_ssm_parameter" "email_to" { 14 | name = "${var.environment}-email-to" 15 | type = "String" 16 | value = var.email_to 17 | } 18 | 19 | resource "aws_ssm_parameter" "smtp_server" { 20 | name = "${var.environment}-smtp-server" 21 | type = "String" 22 | value = var.smtp_server 23 | } 24 | 25 | resource "aws_ssm_parameter" "message_bird_api_key" { 26 | name = "${var.environment}-message-bird-api-key" 27 | type = "String" 28 | value = var.message_bird_api_key 29 | } 30 | 31 | resource "aws_ssm_parameter" "sms_phone_from" { 32 | name = "${var.environment}-sms-phone-from" 33 | type = "String" 34 | value = var.sms_phone_from 35 | } 36 | 37 | resource "aws_ssm_parameter" "sms_phone_to" { 38 | name = "${var.environment}-sms-phone-to" 39 | type = "String" 40 | value = var.sms_phone_to 41 | } -------------------------------------------------------------------------------- /terraform/infra/system/variables.tf: -------------------------------------------------------------------------------- 1 | variable "environment" { 2 | 3 | } 4 | 5 | variable "email_from" { 6 | 7 | } 8 | 9 | variable "email_from_password" { 10 | 11 | } 12 | 13 | variable "email_to" { 14 | 15 | } 16 | 17 | variable "smtp_server" { 18 | 19 | } 20 | 21 | variable "message_bird_api_key" { 22 | 23 | } 24 | 25 | variable "sms_phone_from" { 26 | 27 | } 28 | 29 | variable "sms_phone_to" { 30 | 31 | } -------------------------------------------------------------------------------- /terraform/infra/users/dynamodb-users.tf: -------------------------------------------------------------------------------- 1 | resource "aws_dynamodb_table" "users" { 2 | name = "${var.environment}-users" 3 | hash_key = "id" 4 | attribute { 5 | name = "id" 6 | type = "S" 7 | } 8 | write_capacity = var.write_capacity 9 | read_capacity = var.read_capacity 10 | 11 | attribute { 12 | name = "email" 13 | type = "S" 14 | } 15 | 16 | global_secondary_index { 17 | name = "${var.environment}-email-gsi" 18 | projection_type = "ALL" 19 | hash_key = "email" 20 | write_capacity = var.write_capacity 21 | read_capacity = var.read_capacity 22 | } 23 | } 24 | 25 | resource "aws_dynamodb_table_item" "admin" { 26 | table_name = aws_dynamodb_table.users.name 27 | hash_key = aws_dynamodb_table.users.hash_key 28 | 29 | item = <