From fb2325894b02655f42fbed44797a2200896ba31c Mon Sep 17 00:00:00 2001 From: John Wang Date: Sun, 28 Jan 2018 16:31:41 -0800 Subject: [PATCH] add aws/aws-lambda-go support --- .gitignore | 3 +- README.md | 34 +++++++ aws-package.sh | 4 + examples/proxy_send/proxy_send.go | 57 +++++++---- server.go => main.go | 48 ++++++++-- src/adapters/adapter.go | 10 +- src/adapters/glip.go | 7 +- src/adapters/slack.go | 10 +- .../examples/decode_html/decode_aha_html.go | 14 +-- src/handlers/aha/handler_aha_out.go | 11 ++- src/handlers/base_handler.go | 37 +++++--- src/handlers/handler_home.go | 8 +- src/models/models.go | 94 +++++++++++++++++-- 13 files changed, 265 insertions(+), 72 deletions(-) create mode 100644 aws-package.sh rename server.go => main.go (91%) diff --git a/.gitignore b/.gitignore index d42542c..7239fb7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ _* handler.so handler.zip -server \ No newline at end of file +main +main.zip diff --git a/README.md b/README.md index 78dce40..1342048 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,40 @@ Example Webhook Message from Travis CI: $ go get github.com/grokify/chathooks ``` +## Configuration + +### Environment Variables + +Chathooks uses two environment variables: + +| Variable Name | Value | +|---------------|-------| +| `CHATHOOKS_ENGINE` | The engine to be used: `aws` for `aws/aws-lambda-go`, `nethttp` for `net/http` and `fasthttp` for `valyala/fasthttp` | +| `CHATHOOKS_TOKENS` | Comma-delimited list of verification tokens. No extra leading or trailing spaces. | + +### Engines + +Chathooks supports 4 server engines: + +* `aws/aws-lambda-go` +* `eawsy/aws-lambda-go` (deprecated) +* `net/http` +* `valyala/fasthttp` + +For `aws/aws-lambda-go`, `net/http`, `valyala/fasthttp`, you can select the engine by setting the `CHATHOOKS_ENGINE` environment variable to one of: `["aws", "nethttp", "fasthttp"]`. + +#### Using the AWS Engine + +##### Update Lambda Code: + +You can update the Lambda funciton code using the following: + +https://docs.aws.amazon.com/cli/latest/reference/lambda/update-function-code.html + +`$ aws lambda update-function-code --function-name='MyFunction' --zip-file='fileb://main.zip' --publish --region='us-east-1'` + +Make sure to set your AWS credentials file. + ## Usage ### Starting the Service using FastHTTP diff --git a/aws-package.sh b/aws-package.sh new file mode 100644 index 0000000..a1310e2 --- /dev/null +++ b/aws-package.sh @@ -0,0 +1,4 @@ +rm main +rm main.zip +GOOS=linux go build -o main main.go +zip main.zip main \ No newline at end of file diff --git a/examples/proxy_send/proxy_send.go b/examples/proxy_send/proxy_send.go index a98c756..84c3594 100644 --- a/examples/proxy_send/proxy_send.go +++ b/examples/proxy_send/proxy_send.go @@ -5,16 +5,19 @@ import ( "flag" "fmt" "io/ioutil" - "net/url" "os" "path" "regexp" "strings" + "github.com/google/go-querystring/query" + "github.com/grokify/gotilla/fmt/fmtutil" + "github.com/grokify/chathooks/src/config" + "github.com/grokify/chathooks/src/models" + "github.com/grokify/gotilla/io/ioutilmore" "github.com/grokify/gotilla/net/httputilmore" - "github.com/grokify/gotilla/net/urlutil" "github.com/grokify/gotilla/strings/stringsutil" "github.com/valyala/fasthttp" ) @@ -27,9 +30,7 @@ const ( type ExampleWebhookSender struct { DocHandlersDir string BaseUrl string - OutputType string - Token string - Url string + RequestParams models.RequestParams } func (s *ExampleWebhookSender) SendExamplesForInputType(inputType string) error { @@ -52,20 +53,29 @@ func (s *ExampleWebhookSender) SendExamplesForInputType(inputType string) error return nil } +func BuildURLQueryString(baseUrl string, qry interface{}) string { + v, _ := query.Values(qry) + qryString := v.Encode() + if len(qryString) > 0 { + return baseUrl + "?" + qryString + } + return baseUrl +} + func (s *ExampleWebhookSender) SendExampleForFilepath(filepath string, inputType string) error { bytes, err := ioutil.ReadFile(filepath) if err != nil { return err } - qry := url.Values{} - qry.Add("inputType", inputType) - qry.Add("outputType", s.OutputType) - qry.Add("token", s.Token) - qry.Add("url", s.Url) - - fullUrl := urlutil.BuildURL(s.BaseUrl, qry) - fmt.Println(fullUrl) + qry := models.RequestParams{ + InputType: inputType, + OutputType: s.RequestParams.OutputType, + Token: s.RequestParams.Token, + URL: s.RequestParams.URL, + } + fullUrl := BuildURLQueryString(s.BaseUrl, qry) + fmt.Printf("FULL_URL: %v\n", fullUrl) req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() @@ -79,7 +89,7 @@ func (s *ExampleWebhookSender) SendExampleForFilepath(filepath string, inputType err = fastClient.Do(req, resp) fmt.Printf("RES_STATUS: %v\n", resp.StatusCode()) - if resp.StatusCode() > 299 { + if resp.StatusCode() >= 300 || 1 == 1 { fmt.Printf("RES_BODY: %v\n", string(resp.Body())) } fasthttp.ReleaseRequest(req) @@ -91,19 +101,26 @@ func main() { inputTypeP := flag.String("inputType", "travisci", "Example message type") urlP := flag.String("url", "https://hooks.glip.com/webhook/11112222-3333-4444-5555-666677778888", "Your Webhook URL") outputTypeP := flag.String("outputType", "glip", "Adapter name") + tokenP := flag.String("token", "token", "You token") flag.Parse() inputTypes := strings.ToLower(strings.TrimSpace(*inputTypeP)) + qry := models.RequestParams{ + InputType: *inputTypeP, + OutputType: *outputTypeP, + Token: *tokenP, + URL: *urlP, + } + fmtutil.PrintJSON(qry) + sender := ExampleWebhookSender{ DocHandlersDir: config.DocsHandlersDir(), - BaseUrl: "http://localhost:8080/hooks", - OutputType: strings.ToLower(strings.TrimSpace(*outputTypeP)), - Token: "hello-world", - Url: strings.TrimSpace(*urlP), + BaseUrl: "http://localhost:8080/hook", + RequestParams: qry, } - if len(sender.Url) == 0 { - sender.Url = os.Getenv(WebhookUrlEnvGlip) + if len(sender.RequestParams.URL) == 0 { + sender.RequestParams.URL = os.Getenv(WebhookUrlEnvGlip) } examples := stringsutil.SliceTrimSpace(strings.Split(inputTypes, ",")) diff --git a/server.go b/main.go similarity index 91% rename from server.go rename to main.go index 34489e8..1031537 100644 --- a/server.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "os" "strings" "github.com/aws/aws-lambda-go/events" @@ -14,6 +15,8 @@ import ( "github.com/grokify/gotilla/fmt/fmtutil" fhu "github.com/grokify/gotilla/net/fasthttputil" nhu "github.com/grokify/gotilla/net/nethttputil" + "github.com/grokify/gotilla/strings/stringsutil" + "github.com/joho/godotenv" log "github.com/sirupsen/logrus" "github.com/valyala/fasthttp" @@ -50,12 +53,21 @@ import ( "github.com/grokify/chathooks/src/handlers/victorops" ) +/* + +Use the `CHATHOOKS_TOKENS` environment variable to load secret +tokens as a comma delimited string. + +*/ + const ( ParamNameInput = "inputType" ParamNameOutput = "outputType" ParamNameURL = "url" ParamNameToken = "token" - SecretToken = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + EnvEnvPath = "ENV_PATH" + EnvEngine = "CHATHOOKS_ENGINE" // aws, nethttp, fasthttp + EnvTokens = "CHATHOOKS_TOKENS" ) type HandlerSet struct { @@ -63,8 +75,8 @@ type HandlerSet struct { } type Handler interface { - HandleAwsLambda(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) HandleCanonical(hookData models.HookData) []models.ErrorInfo + HandleAwsLambda(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) HandleEawsyLambda(event *apigatewayproxyevt.Event, ctx *runtime.Context) (events.APIGatewayProxyResponse, error) HandleFastHTTP(ctx *fasthttp.RequestCtx) HandleNetHTTP(res http.ResponseWriter, req *http.Request) @@ -157,13 +169,19 @@ func getConfig() ServiceInfo { "victorops": hf.InflateHandler(victorops.NewHandler()), }} - return ServiceInfo{ + svcInfo := ServiceInfo{ Config: cfgData, AdapterSet: adapterSet, HandlerSet: handlerSet, RequireToken: false, - Tokens: map[string]int{SecretToken: 1}, + Tokens: map[string]int{}, } + tokens := stringsutil.SplitCondenseSpace(os.Getenv(EnvTokens), ",") + for _, token := range tokens { + svcInfo.Tokens[token] = 1 + } + + return svcInfo } var serviceInfo = getConfig() @@ -272,10 +290,12 @@ func (h *AnyHTTPHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) { token := fhu.GetReqHeader(ctx, ParamNameToken) if len(token) == 0 { ctx.SetStatusCode(http.StatusUnauthorized) + log.Warn("E_NO_TOKEN") return } if _, ok := serviceInfo.Tokens[token]; !ok { ctx.SetStatusCode(http.StatusUnauthorized) + log.Warn("E_INCORRECT_TOKEN") return } } @@ -299,7 +319,6 @@ func serveNetHttp() { func serveFastHttp() { router := fasthttprouter.New() - router.GET("/", handlers.HomeHandler) router.GET("/hook", anyHTTPHandler.HandleFastHTTP) router.GET("/hook/", anyHTTPHandler.HandleFastHTTP) router.POST("/hook", anyHTTPHandler.HandleFastHTTP) @@ -313,7 +332,20 @@ func serveAwsLambda() { } func main() { - //serveAwsLambda() - //serveNetHttp() - serveFastHttp() + if len(strings.TrimSpace(os.Getenv(EnvEnvPath))) > 0 { + err := godotenv.Load(os.Getenv(EnvEnvPath)) + if err != nil { + log.Fatal("Error loading .env file") + } + } + + engine := strings.ToLower(strings.TrimSpace(os.Getenv(EnvEngine))) + switch engine { + case "aws": + serveAwsLambda() + case "nethttp": + serveNetHttp() + case "fasthttp": + serveFastHttp() + } } diff --git a/src/adapters/adapter.go b/src/adapters/adapter.go index 6b95c21..71aa6bd 100644 --- a/src/adapters/adapter.go +++ b/src/adapters/adapter.go @@ -22,13 +22,15 @@ func (set *AdapterSet) SendWebhooks(hookData models.HookData) []models.ErrorInfo errs := []models.ErrorInfo{} if len(hookData.OutputType) > 0 && len(hookData.OutputURL) > 0 { if adapter, ok := set.Adapters[hookData.OutputType]; ok { - req, res, err := adapter.SendWebhook(hookData.OutputURL, hookData.OutputMessage) + var msg interface{} + req, res, err := adapter.SendWebhook(hookData.OutputURL, hookData.CanonicalMessage, &msg) errs = set.procResponse(errs, req, res, err) } } for _, namedAdapter := range hookData.OutputNames { if adapter, ok := set.Adapters[namedAdapter]; ok { - req, res, err := adapter.SendMessage(hookData.OutputMessage) + var msg interface{} + req, res, err := adapter.SendMessage(hookData.CanonicalMessage, &msg) errs = set.procResponse(errs, req, res, err) } } @@ -50,8 +52,8 @@ func (set *AdapterSet) procResponse(errs []models.ErrorInfo, req *fasthttp.Reque } type Adapter interface { - SendWebhook(url string, message cc.Message) (*fasthttp.Request, *fasthttp.Response, error) - SendMessage(message cc.Message) (*fasthttp.Request, *fasthttp.Response, error) + SendWebhook(url string, message cc.Message, finalMsg interface{}) (*fasthttp.Request, *fasthttp.Response, error) + SendMessage(message cc.Message, finalMsg interface{}) (*fasthttp.Request, *fasthttp.Response, error) WebhookUID(ctx *fasthttp.RequestCtx) (string, error) } diff --git a/src/adapters/glip.go b/src/adapters/glip.go index 03596f4..dc67d11 100644 --- a/src/adapters/glip.go +++ b/src/adapters/glip.go @@ -39,8 +39,9 @@ func NewGlipAdapter(webhookURLOrUID string) (*GlipAdapter, error) { CommonConverter: converter}, err } -func (adapter *GlipAdapter) SendWebhook(urlOrUid string, message cc.Message) (*fasthttp.Request, *fasthttp.Response, error) { +func (adapter *GlipAdapter) SendWebhook(urlOrUid string, message cc.Message, glipmsg interface{}) (*fasthttp.Request, *fasthttp.Response, error) { glipMessage := adapter.CommonConverter.ConvertCommonMessage(message) + glipmsg = &glipMessage glipMessageString, err := json.Marshal(glipMessage) if err == nil { @@ -51,8 +52,8 @@ func (adapter *GlipAdapter) SendWebhook(urlOrUid string, message cc.Message) (*f return adapter.GlipClient.PostWebhookGUIDFast(urlOrUid, glipMessage) } -func (adapter *GlipAdapter) SendMessage(message cc.Message) (*fasthttp.Request, *fasthttp.Response, error) { - return adapter.SendWebhook(adapter.WebhookURLOrUID, message) +func (adapter *GlipAdapter) SendMessage(message cc.Message, glipmsg interface{}) (*fasthttp.Request, *fasthttp.Response, error) { + return adapter.SendWebhook(adapter.WebhookURLOrUID, message, glipmsg) } func (adapter *GlipAdapter) WebhookUID(ctx *fasthttp.RequestCtx) (string, error) { diff --git a/src/adapters/slack.go b/src/adapters/slack.go index f7e5d92..c11737d 100644 --- a/src/adapters/slack.go +++ b/src/adapters/slack.go @@ -21,12 +21,14 @@ func NewSlackAdapter(webhookURLOrUID string) (*SlackAdapter, error) { WebhookURLOrUID: webhookURLOrUID}, err } -func (adapter *SlackAdapter) SendWebhook(urlOrUid string, message commonchat.Message) (*fasthttp.Request, *fasthttp.Response, error) { - return adapter.SlackClient.PostWebhookFast(urlOrUid, slack.ConvertCommonMessage(message)) +func (adapter *SlackAdapter) SendWebhook(urlOrUid string, message commonchat.Message, slackmsg interface{}) (*fasthttp.Request, *fasthttp.Response, error) { + slackMessage := slack.ConvertCommonMessage(message) + slackmsg = &slackMessage + return adapter.SlackClient.PostWebhookFast(urlOrUid, slackMessage) } -func (adapter *SlackAdapter) SendMessage(message commonchat.Message) (*fasthttp.Request, *fasthttp.Response, error) { - return adapter.SendWebhook(adapter.WebhookURLOrUID, message) +func (adapter *SlackAdapter) SendMessage(message commonchat.Message, slackmsg interface{}) (*fasthttp.Request, *fasthttp.Response, error) { + return adapter.SendWebhook(adapter.WebhookURLOrUID, message, slackmsg) } func (adapter *SlackAdapter) WebhookUID(ctx *fasthttp.RequestCtx) (string, error) { diff --git a/src/handlers/aha/examples/decode_html/decode_aha_html.go b/src/handlers/aha/examples/decode_html/decode_aha_html.go index 9e0b089..40950dc 100644 --- a/src/handlers/aha/examples/decode_html/decode_aha_html.go +++ b/src/handlers/aha/examples/decode_html/decode_aha_html.go @@ -10,6 +10,7 @@ import ( func main() { data := []byte(`{"event":"audit","audit":{"id":"1011112222333344445555666"}}`) + data = []byte(`{"event":"audit","audit":{"id":"6514922586902400152","audit_action":"update","created_at":"2018-01-25T09:46:59.257Z","interesting":true,"user":{"id":"6355516420883588191","name":"John Wang","email":"john.wang@ringcentral.com","created_at":"2016-11-21T20:09:39.022Z","updated_at":"2018-01-24T23:50:12.097Z"},"auditable_type":"note","auditable_id":"6499593214164077189","description":"updated feature RCGG-112 User feedback collection","auditable_url":"https://ringcentral.aha.io/features/RCGG-112","changes":[{"field_name":"Description","value":"\u003cp\u003eBesides Google Chrome store reviews, we should provide user way to submit their feedback from within our Google app\u003c/p\u003e\u003cp\u003e\u003cb\u003eRequirement\u003c/b\u003e\u003c/p\u003e\u003col\u003e\n\u003cli\u003eThere should be a menu item 'Feedback' on Settings page\u003c/li\u003e\n\u003cli\u003eWhen user clicks 'Feedback', user shall be navigated to a new page with following content\u003cblockquote\u003e\n\u003cp\u003e\u003cb\u003eContact Customer Support\u003c/b\u003e\u003cbr\u003e Your feedback is valuable for us. If you have problems using the app, want to request a feature, or report a bug, we’re more than happy to help. Please fill in the form below and click \u003ci\u003eSend Your Feedback\u003c/i\u003e, or directly use your mailbox and send your request to integration.service@ringcentral.com.\u003c/p\u003e\n\u003cp\u003eYour email address (so we can reply to you)\u003cbr\u003e [Input box: ronald.app@ringcentral.com]\u003c/p\u003e\n\u003cp\u003eFeedback topic\u003cbr\u003e [Dropdown: Please select an option]\u003c/p\u003e\n\u003cp\u003eSubject\u003cbr\u003e [Input box: Let us know how we can help you]\u003c/p\u003e\n\u003cp\u003eFull description\u003cbr\u003e [Input box: Please include as much information as possible]\u003c/p\u003e\n\u003cp\u003e\u003cstrike\u003eAttachment\u003c/strike\u003e \u003cbr\u003e \u003cstrike\u003e[Drag and drop area]\u003c/strike\u003e\u003c/p\u003e\n\u003cp\u003e[Button: Send Your Feedback]\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003eFeedback topic options in the dropdown list: Please select an option (default) | Bug report | Feature request | Others\u003c/li\u003e\n\u003cli\u003eThere should be back icon on the page by clicking which user can be navigated back to Settings page.\u003c/li\u003e\n\u003cli\u003eWe should leverage an Email server and send the feedback including all the information/\u003cstrike\u003eattachment\u003c/strike\u003e user submitted to team alias \u003ci\u003eintegration.service@ringcentral.com\u003c/i\u003e with title 'Google User Feedback'.\u003cul\u003e\u003cli\u003eEmail content example\u003cblockquote\u003e\n\u003cp\u003eHi Integration Team,\u003c/p\u003e\n\u003cp\u003eYou've got feedback from customer on RingCentral for Google extension. This customer could be contacted via email [customer's email address].\u003c/p\u003e\n\u003cp\u003e\u003cb\u003eCustomer Feedback Topic\u003c/b\u003e\u003cbr\u003e *****\u003c/p\u003e\n\u003cp\u003e\u003cb\u003eSubject\u003c/b\u003e\u003cbr\u003e ******\u003c/p\u003e\n\u003cp\u003e\u003cb\u003eDescription\u003c/b\u003e\u003cbr\u003e ********\u003c/p\u003e\n\u003cp\u003eRegards,\u003cbr\u003e RingCentral for Google Extension\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\u003cp\u003ePlease note that the feedback email should support rebranding, to avoid us using wrong words when reaching back to the customers.\u003c/p\u003e"}]}}`) msg, err := aha.AhaOutMessageFromBytes(data) if err != nil { @@ -19,13 +20,14 @@ func main() { val := msg.Audit.Changes[0].Value fmt.Println(val) - fmt.Println("---") - val2 := html.UnescapeString(val) - fmt.Println(val2) - fmt.Println("---") //p := bluemonday.UGCPolicy() p := bluemonday.StrictPolicy() - val3 := p.Sanitize(val2) - fmt.Println(val3) + val = p.Sanitize(val) + fmt.Println(val) + + fmt.Println("===") + val = html.UnescapeString(val) + fmt.Println(val) + } diff --git a/src/handlers/aha/handler_aha_out.go b/src/handlers/aha/handler_aha_out.go index 03ee7a5..623464f 100644 --- a/src/handlers/aha/handler_aha_out.go +++ b/src/handlers/aha/handler_aha_out.go @@ -43,14 +43,14 @@ func Normalize(cfg config.Configuration, bytes []byte) (cc.Message, error) { p := bluemonday.StrictPolicy() - if src.Audit != nil { + if src.Audit != nil && len(src.Audit.Changes) > 0 { + attachment := cc.NewAttachment() for _, change := range src.Audit.Changes { - attachment := cc.NewAttachment() field := cc.Field{} key := strings.TrimSpace(change.FieldName) val := strings.TrimSpace(change.Value) - val = html.UnescapeString(val) val = p.Sanitize(val) + val = html.UnescapeString(val) addField := false if len(key) > 0 { field.Title = key @@ -60,11 +60,14 @@ func Normalize(cfg config.Configuration, bytes []byte) (cc.Message, error) { field.Value = val addField = true } + if key != "Description" { + field.Short = true + } if addField { attachment.AddField(field) - ccMsg.AddAttachment(attachment) } } + ccMsg.AddAttachment(attachment) } return ccMsg, nil diff --git a/src/handlers/base_handler.go b/src/handlers/base_handler.go index edcd49d..835cfaf 100644 --- a/src/handlers/base_handler.go +++ b/src/handlers/base_handler.go @@ -33,14 +33,18 @@ type Normalize func(config.Configuration, []byte) (cc.Message, error) func (h Handler) HandleAwsLambda(ctx context.Context, awsReq events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { hookData := models.HookDataFromAwsLambdaEvent(h.MessageBodyType, awsReq) errs := h.HandleCanonical(hookData) - return models.ErrorInfosToAwsAPIGatewayProxyResponse(errs...), nil + awsRes, err := models.BuildAwsAPIGatewayProxyResponse(hookData, errs...) + return awsRes, err + //return models.ErrorInfosToAwsAPIGatewayProxyResponse(errs...), nil } // HandleEawsyLambda is the method to respond to a fasthttp request. func (h Handler) HandleEawsyLambda(event *apigatewayproxyevt.Event, ctx *runtime.Context) (events.APIGatewayProxyResponse, error) { hookData := models.HookDataFromEawsyLambdaEvent(h.MessageBodyType, event) errs := h.HandleCanonical(hookData) - return models.ErrorInfosToAwsAPIGatewayProxyResponse(errs...), nil + awsRes, err := models.BuildAwsAPIGatewayProxyResponse(hookData, errs...) + return awsRes, err + //return models.ErrorInfosToAwsAPIGatewayProxyResponse(errs...), nil } // HandleNetHTTP is the method to respond to a fasthttp request. @@ -48,9 +52,15 @@ func (h Handler) HandleNetHTTP(res http.ResponseWriter, req *http.Request) { hookData := models.HookDataFromNetHTTPReq(h.MessageBodyType, req) errs := h.HandleCanonical(hookData) - resInfo := models.ErrorsInfoToResponseInfo(errs...) - res.WriteHeader(resInfo.StatusCode) - res.Write(resInfo.Body) + awsRes, err := models.BuildAwsAPIGatewayProxyResponse(hookData, errs...) + //return awsRes, err + //resInfo := models.ErrorsInfoToResponseInfo(errs...) + if err != nil { + res.WriteHeader(awsRes.StatusCode) + res.Write([]byte(awsRes.Body)) + } else { + res.WriteHeader(http.StatusInternalServerError) + } } // HandleFastHTTP is the method to respond to a fasthttp request. @@ -58,10 +68,16 @@ func (h Handler) HandleFastHTTP(ctx *fasthttp.RequestCtx) { hookData := models.HookDataFromFastHTTPReqCtx(h.MessageBodyType, ctx) errs := h.HandleCanonical(hookData) - proxyOutput := models.ErrorInfosToAwsAPIGatewayProxyOutput(errs...) - ctx.SetStatusCode(proxyOutput.StatusCode) - if proxyOutput.StatusCode > 399 { - fmt.Fprintf(ctx, "%s", proxyOutput.Body) + awsRes, err := models.BuildAwsAPIGatewayProxyResponse(hookData, errs...) + + if err != nil { + ctx.SetStatusCode(http.StatusInternalServerError) + log.WithFields(log.Fields{ + "event": "incoming.webhook.error", + "handler": err.Error()}).Info("ERROR") + } else { + ctx.SetStatusCode(awsRes.StatusCode) + fmt.Fprint(ctx, awsRes.Body) } } @@ -77,7 +93,6 @@ func (h Handler) HandleCanonical(hookData models.HookData) []models.ErrorInfo { ccMsg, err := h.Normalize(h.Config, hookData.InputBody) if err != nil { - //ctx.SetStatusCode(fasthttp.StatusNotAcceptable) log.WithFields(log.Fields{ "type": "http.response", "status": fasthttp.StatusNotAcceptable, @@ -85,6 +100,6 @@ func (h Handler) HandleCanonical(hookData models.HookData) []models.ErrorInfo { }).Info(fmt.Sprintf("%v request conversion failed.", DisplayName)) return []models.ErrorInfo{{StatusCode: 500, Body: []byte(err.Error())}} } - hookData.OutputMessage = ccMsg + hookData.CanonicalMessage = ccMsg return h.AdapterSet.SendWebhooks(hookData) } diff --git a/src/handlers/handler_home.go b/src/handlers/handler_home.go index 1275347..899c876 100644 --- a/src/handlers/handler_home.go +++ b/src/handlers/handler_home.go @@ -2,12 +2,12 @@ package handlers import ( "fmt" - "strings" + //"strings" - "github.com/eawsy/aws-lambda-go-event/service/lambda/runtime/event/apigatewayproxyevt" + //"github.com/eawsy/aws-lambda-go-event/service/lambda/runtime/event/apigatewayproxyevt" "github.com/grokify/chathooks/src/adapters" "github.com/grokify/chathooks/src/config" - "github.com/grokify/gotilla/strings/stringsutil" + //"github.com/grokify/gotilla/strings/stringsutil" "github.com/valyala/fasthttp" ) @@ -29,6 +29,7 @@ type Configuration struct { AdapterSet adapters.AdapterSet } +/* type HookRequestData struct { InputType string InputBody []byte @@ -70,3 +71,4 @@ func HookRequestDataFromFastHTTPReqCtx(ctx *fasthttp.RequestCtx) HookRequestData string(ctx.QueryArgs().Peek(QueryParamNamedOutputs)), ",")), } } +*/ diff --git a/src/models/models.go b/src/models/models.go index cb52f54..24fa54d 100644 --- a/src/models/models.go +++ b/src/models/models.go @@ -8,6 +8,8 @@ import ( "net/url" "strings" + "github.com/grokify/gotilla/fmt/fmtutil" + "github.com/aws/aws-lambda-go/events" cc "github.com/commonchat/commonchat-go" "github.com/eawsy/aws-lambda-go-event/service/lambda/runtime/event/apigatewayproxyevt" @@ -25,6 +27,13 @@ const ( QueryParamOutputURL = "url" ) +type RequestParams struct { + InputType string `url:"inputType"` + OutputType string `url:"outputType"` + Token string `url:"token"` + URL string `url:"url"` +} + type MessageBodyType int const ( @@ -42,14 +51,14 @@ var intervals = [...]string{ } type HookData struct { - InputType string - InputBody []byte - OutputType string - OutputURL string - OutputNames []string - Token string - InputMessage []byte - OutputMessage cc.Message + InputType string `json:inputType,omitempty"` + InputBody []byte `json:inputBody,omitempty"` + OutputType string `json:outputType,omitempty"` + OutputURL string `json:outputUrl,omitempty"` + OutputNames []string `json:outputNames,omitempty"` + Token string `json:token,omitempty"` + InputMessage []byte `json:inputMessage,omitempty"` + CanonicalMessage cc.Message `json:canonicalMessage,omitempty"` } type hookDataRequest struct { @@ -223,7 +232,55 @@ type ErrorInfo struct { Body []byte } +type ResponseInfo struct { + HookData HookData `json:hookData,omitempty"` + Responses []ErrorInfo `json:"responses,omitempty"` + StatusCode int `json:"statusCode,omitempty"` + //URL string `json:"url,omitempty"` + //Body interface{} `json:"body,omitempty"` + //InputType string `json:"inputType,omitempty"` + //OutputType string `json:"outputType,omitempty"` +} + +func (ri *ResponseInfo) ToAPIGatewayProxyResponse() (events.APIGatewayProxyResponse, error) { + res := events.APIGatewayProxyResponse{ + StatusCode: ri.StatusCode, + } + + bodyBytes, err := json.Marshal(ri) + if err != nil { + return res, nil + } + res.Body = string(bodyBytes) + + return res, nil +} + +/* func ErrorsInfoToResponseInfo(errs ...ErrorInfo) ErrorInfo { + resInfo := ResponseInfo{ + Responses: errs, + } + return resInfo +} +*/ + +func GetMaxStatusCode(errs ...ErrorInfo) int { + if len(errs) == 0 { + return http.StatusOK + } else if len(errs) == 1 { + return errs[0].StatusCode + } + maxStatus := 200 + for _, errInfo := range errs { + if errInfo.StatusCode > maxStatus { + maxStatus = errInfo.StatusCode + } + } + return maxStatus +} + +func ErrorsInfoToResponseInfoOld(errs ...ErrorInfo) ErrorInfo { resInfo := ErrorInfo{} if len(errs) == 0 { resInfo.StatusCode = http.StatusOK @@ -249,6 +306,7 @@ func ErrorsInfoToResponseInfo(errs ...ErrorInfo) ErrorInfo { return resInfo } +/* //func ErrorInfosToAlexaResponse(errs ...ErrorInfo) AwsAPIGatewayProxyOutput { func ErrorInfosToAwsAPIGatewayProxyOutput(errs ...ErrorInfo) AwsAPIGatewayProxyOutput { resInfo := ErrorsInfoToResponseInfo() @@ -258,7 +316,26 @@ func ErrorInfosToAwsAPIGatewayProxyOutput(errs ...ErrorInfo) AwsAPIGatewayProxyO Body: string(resInfo.Body), } } +*/ + +func BuildAwsAPIGatewayProxyResponse(hookData HookData, errs ...ErrorInfo) (events.APIGatewayProxyResponse, error) { + resInfo := ResponseInfo{ + HookData: hookData, + Responses: errs, + StatusCode: GetMaxStatusCode(errs...), + } + fmtutil.PrintJSON(resInfo) + return resInfo.ToAPIGatewayProxyResponse() + //resInfo := ErrorsInfoToResponseInfo() + /* + return events.APIGatewayProxyResponse{ + StatusCode: resInfo.StatusCode, + Body: string(resInfo.Body), + } + */ +} +/* func ErrorInfosToAwsAPIGatewayProxyResponse(errs ...ErrorInfo) events.APIGatewayProxyResponse { resInfo := ErrorsInfoToResponseInfo() @@ -267,3 +344,4 @@ func ErrorInfosToAwsAPIGatewayProxyResponse(errs ...ErrorInfo) events.APIGateway Body: string(resInfo.Body), } } +*/