diff --git a/community/boilerplates/serverless-triggers/aws-lambda/go/echo/README.md b/community/boilerplates/serverless-triggers/aws-lambda/go/echo/README.md new file mode 100644 index 00000000000..3c4df1b12b5 --- /dev/null +++ b/community/boilerplates/serverless-triggers/aws-lambda/go/echo/README.md @@ -0,0 +1,37 @@ +# Go Echo Example for AWS Lambda + +### Setup tables +1. Create table: + +``` +notes: + id: Integer (auto-increment) + note: Text + + Primary key: id +``` + +### Setup AWS Lambda +Create a Lambda function in AWS. This will be our webhook. + +1. Sign in to the AWS Management Console and open the AWS Lambda console. +2. Choose "Create a function" under the **Get Started** section. +3. Select "Author from scratch". +4. Specify the **Name** for your lambda. +5. Select go 1.x as the **Runtime**. +6. In **Role** choose **Create new role from template(s)** +7. In **Role name**, enter a name for your role, leave the **Policy Templates** field blank. +8. Build and upload the code to AWS Lambda: + + i. Run the bash script provided + + `bash build.sh` + + ii. Upload *echo.zip* in the lambda console. +10. Click the **save** button for the changes to take effect. +11. Create an API with a lambda proxy integration as specified in the [AWS Documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html#api-gateway-create-api-as-simple-proxy-for-lambda-build). + +### Add the trigger in Hasura GraphQL +1. In events tab, add a trigger +2. Select all insert, update, delete operations for the trigger. +3. Paste the API endpoint of your AWS lambda as the webhook. diff --git a/community/boilerplates/serverless-triggers/aws-lambda/go/echo/build.sh b/community/boilerplates/serverless-triggers/aws-lambda/go/echo/build.sh new file mode 100644 index 00000000000..b55c56015cb --- /dev/null +++ b/community/boilerplates/serverless-triggers/aws-lambda/go/echo/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +echo "Fetching dependencies" + +go get . + +echo "Building binary" + +env GOOS=linux GOARCH=amd64 go build + +echo "Binary build complete. Zipping output." + +zip -j ./mutation.zip mutation + +echo "Zip complete. You can now upload the zip file" diff --git a/community/boilerplates/serverless-triggers/aws-lambda/go/echo/echo.go b/community/boilerplates/serverless-triggers/aws-lambda/go/echo/echo.go new file mode 100644 index 00000000000..8e2086d38bc --- /dev/null +++ b/community/boilerplates/serverless-triggers/aws-lambda/go/echo/echo.go @@ -0,0 +1,83 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" +) + +type TableStruct struct { + Name string `json:"name"` +} + +type EventData struct { + Old map[string]interface{} `json:"old"` + New map[string]interface{} `json"new"` +} + +type EventStruct struct { + Operation string `json:"op"` + Data EventData `json:"data"` +} + +type HasuraEvent struct { + Table *TableStruct `json:"table"` + Event *EventStruct `json:"event"` + Op string `json:"op"` +} + +// Handler for the Lambda function to echo back the data +func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + + // stdout and stderr are sent to AWS CloudWatch Logs + log.Printf("Processing Lambda request %+v\n", request.Body) + + // parse json body + body := &HasuraEvent{ + Table: &TableStruct{}, + Event: &EventStruct{}, + } + + err := json.Unmarshal([]byte(request.Body), body) + + if err != nil { + message := map[string]string{ + "message": "Unable to parse Hasura Event", + } + + responseBody, _ := json.Marshal(message) + + return events.APIGatewayProxyResponse{ + Body: string(responseBody), + StatusCode: 400, + }, nil + } + + var message = "cannot process request" + var data = body.Event.Data + + if body.Table.Name == "notes" { + switch body.Event.Operation { + case "INSERT": + message = fmt.Sprintf("New note %v inserted, with data: %v", data.New["id"], data.New["note"]) + case "UPDATE": + message = fmt.Sprintf("New note %v updated, with data: %v", data.New["id"], data.New["note"]) + case "DELETE": + message = fmt.Sprintf("New note %v delete, with data: %v", data.Old["id"], data.Old["note"]) + } + } + + resposeBody, _ := json.Marshal(message) + + return events.APIGatewayProxyResponse{ + Body: string(resposeBody), + StatusCode: 200, + }, nil +} + +func main() { + lambda.Start(Handler) +} diff --git a/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/README.md b/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/README.md new file mode 100644 index 00000000000..8e529d6ef23 --- /dev/null +++ b/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/README.md @@ -0,0 +1,107 @@ +# Golang Mutation Example for AWS Lambda + +This example trigger updates (executes a mutation) when an event occurs on +another table, written in Golang. + +### Create tables + +``` +Table name: note + +Columns: + +id Integer auto-increment +note Text + +Primary key: id +``` + +``` +Table name: note_revision + +Columns: + +id Integer auto-increment +note Text +note_id Integer +update_at Timestamp, default: now() + +Primary key: id +``` + +### Setup AWS Lambda +Create a Lambda function in AWS. This will be our webhook. + +1. Sign in to the AWS Management Console and open the AWS Lambda console. +2. Choose "Create a function" under the **Get Started** section. +3. Select "Author from scratch". +4. Specify the **Name** for your lambda. +5. Select go 1.x as the **Runtime**. +6. In **Role** choose **Create new role from template(s)** +7. In **Role name**, enter a name for your role, leave the **Policy Templates** field blank. +8. Build and upload the code to AWS Lambda: + + i. Run the bash script provided + + `bash build.sh` + + ii. Upload *mutation.zip* in the lambda console. +9. In the Designer pane, add an environment variable `HGE_ENDPOINT` which is the Hasura GraphQL Engine endpoint. +10. Click the **save** button for the changes to take effect. +11. Create an API with a lambda proxy integration as specified in the [AWS Documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html#api-gateway-create-api-as-simple-proxy-for-lambda-build). + +### Add the trigger in Hasura GraphQL +1. In events tab, add a trigger +2. Select all insert, update, delete operations for the trigger. +3. Paste the API endpoint of your AWS lambda as the webhook. + +### Test the trigger + +Goto `Data` tab on Hasura console, browse to `note` table and to the Browse rows +tab. Edit an existing note and check if the `note_revision` entry has been +created. Also, checkout the trigger request and response. + +Trigger payload (request): +```json +{ + "event": { + "op": "UPDATE", + "data": { + "old": { + "note": "note1", + "id": 1 + }, + "new": { + "note": "note1 updated", + "id": 1 + } + } + }, + "created_at": "2018-10-02T06:38:22.67311Z", + "id": "f57a1c79-72ba-4c19-8791-37d1b9616bcf", + "trigger": { + "name": "note_revision_trigger", + "id": "5d85cbd1-c134-45ce-810c-7ecd3b4fc1ee" + }, + "table": { + "schema": "public", + "name": "note" + } +} +``` + +Webhook response: +```json +{ + "data": { + "insert_note_revision": { + "affected_rows": 1, + "returning": [ + { + "id": 2 + } + ] + } + } +} +``` diff --git a/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/build.sh b/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/build.sh new file mode 100644 index 00000000000..b55c56015cb --- /dev/null +++ b/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +echo "Fetching dependencies" + +go get . + +echo "Building binary" + +env GOOS=linux GOARCH=amd64 go build + +echo "Binary build complete. Zipping output." + +zip -j ./mutation.zip mutation + +echo "Zip complete. You can now upload the zip file" diff --git a/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/mutation.go b/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/mutation.go new file mode 100644 index 00000000000..05b6b955fee --- /dev/null +++ b/community/boilerplates/serverless-triggers/aws-lambda/go/mutation/mutation.go @@ -0,0 +1,155 @@ +package main + +import ( + "bytes" + "encoding/json" + "log" + "net/http" + "os" + + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" +) + +type HasuraEvent struct { + ID string `json:"id"` + Event `json:"event"` + Table `json:"table"` + Trigger `json:"trigger"` +} + +type Event struct { + Op string `json:"op"` + Data `json:"data"` +} + +type Data struct { + Old map[string]interface{} `json:"old "` + New map[string]interface{} `json:"new"` +} + +type Table struct { + Name string `json:"name"` + Schema string `json:"schema"` +} + +type Trigger struct { + ID string `json:"id"` + Name string `json:"name"` +} + +const MUTATION_UPDATE_NOTE_REVISION = ` + mutation updateNoteRevision ($object: note_revision_insert_input!) { + insert_note_revision (objects: [$object]) { + affected_rows + returning { + id + } + } + } +` + +func constructErrorResponse(responsePayload map[string]string) (events.APIGatewayProxyResponse, error) { + var responseBody []byte + + responseBody, err := json.Marshal(responsePayload) + var statusCode int = 200 + + if err != nil { + responseBody, _ = json.Marshal(map[string]string{ + "message": "Internal error ocurred while constructing error response", + }) + statusCode = 500 + } + + return events.APIGatewayProxyResponse{ + Body: string(responseBody), + StatusCode: statusCode, + }, nil +} + +var HGE_ENDPOINT = os.Getenv("HGE_ENDPOINT") + +// Handler for the Lambda function to echo back the data +func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + // stdout and stderr are sent to AWS CloudWatch Logs + log.Printf("Processing Lambda request %+v\n", request.Body) + + if len(HGE_ENDPOINT) == 0 { + return constructErrorResponse(map[string]string{ + "message": "HGE Endpoint not defined in environment variable", + }) + } + + // parse json body + var body HasuraEvent + + err := json.Unmarshal([]byte(request.Body), body) + + event := body.Event + + if err != nil { + return constructErrorResponse(map[string]string{ + "message": "Unable to parse Hasura Event", + }) + } + + note_id, ok := event.Data.Old["id"] + if !ok { + return constructErrorResponse(map[string]string{ + "message": "invalid payload: note id not found", + }) + } + note, ok := event.Data.New["note"] + if !ok { + return constructErrorResponse(map[string]string{ + "message": "invalid payload: note not found", + }) + } + + // execute the mutation + payload := map[string]interface{}{ + "query": MUTATION_UPDATE_NOTE_REVISION, + "variables": map[string]interface{}{ + "object": map[string]interface{}{ + "note_id": note_id.(float64), + "note": note.(string), + }, + }, + } + + b := new(bytes.Buffer) + json.NewEncoder(b).Encode(payload) + res, err := http.Post(HGE_ENDPOINT, "application/json; charset=utf-8", b) + if err != nil { + return constructErrorResponse(map[string]string{ + "message": err.Error(), + }) + } + defer res.Body.Close() + var response map[string]interface{} + + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return constructErrorResponse(map[string]string{ + "message": err.Error(), + }) + } + + responseBody, err := json.Marshal(response) + + if err != nil { + return constructErrorResponse(map[string]string{ + "message": err.Error(), + }) + } + + return events.APIGatewayProxyResponse{ + Body: string(responseBody), + StatusCode: 200, + }, nil +} + +func main() { + lambda.Start(Handler) +}