add gin, errors handler and minor fixes
This commit is contained in:
parent
a60209c036
commit
747b73ff87
@ -17,6 +17,7 @@ type TransportConfig struct {
|
||||
Debug bool `yaml:"debug"`
|
||||
UpdateInterval int `yaml:"update_interval"`
|
||||
ConfigAWS ConfigAWS `yaml:"config_aws"`
|
||||
Credentials []string `yaml:"credentials"`
|
||||
}
|
||||
|
||||
// ConfigAWS struct
|
||||
|
@ -20,3 +20,7 @@ config_aws:
|
||||
bucket: ~
|
||||
folder_name: ~
|
||||
content_type: image/jpeg
|
||||
|
||||
credentials:
|
||||
- "/api/integration-modules/{code}"
|
||||
- "/api/integration-modules/{code}/edit"
|
||||
|
@ -20,3 +20,7 @@ config_aws:
|
||||
bucket: ~
|
||||
folder_name: ~
|
||||
content_type: image/jpeg
|
||||
|
||||
credentials:
|
||||
- "/api/integration-modules/{code}"
|
||||
- "/api/integration-modules/{code}/edit"
|
||||
|
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
)
|
||||
@ -17,7 +16,6 @@ type Orm struct {
|
||||
func NewDb(config *TransportConfig) *Orm {
|
||||
db, err := gorm.Open("postgres", config.Database.Connection)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
211
errors.go
Normal file
211
errors.go
Normal file
@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
|
||||
)
|
||||
|
||||
func ErrorHandler(handlers ...ErrorHandlerFunc) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
rec := recover()
|
||||
for _, handler := range handlers {
|
||||
handler(rec, c)
|
||||
}
|
||||
|
||||
if rec != nil || len(c.Errors) > 0 {
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorResponseHandler() ErrorHandlerFunc {
|
||||
return func(recovery interface{}, c *gin.Context) {
|
||||
publicErrors := c.Errors.ByType(gin.ErrorTypePublic)
|
||||
privateLen := len(c.Errors.ByType(gin.ErrorTypePrivate))
|
||||
publicLen := len(publicErrors)
|
||||
|
||||
if privateLen == 0 && publicLen == 0 && recovery == nil {
|
||||
return
|
||||
}
|
||||
|
||||
messagesLen := publicLen
|
||||
if privateLen > 0 || recovery != nil {
|
||||
messagesLen++
|
||||
}
|
||||
|
||||
messages := make([]string, messagesLen)
|
||||
index := 0
|
||||
for _, err := range publicErrors {
|
||||
messages[index] = err.Error()
|
||||
index++
|
||||
}
|
||||
|
||||
if privateLen > 0 || recovery != nil {
|
||||
messages[index] = "Something went wrong"
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": messages})
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorCaptureHandler(client *raven.Client, errorsStacktrace bool) ErrorHandlerFunc {
|
||||
return func(recovery interface{}, c *gin.Context) {
|
||||
tags := map[string]string{
|
||||
"endpoint": c.Request.RequestURI,
|
||||
}
|
||||
|
||||
if recovery != nil {
|
||||
stacktrace := raven.NewStacktrace(4, 3, nil)
|
||||
recStr := fmt.Sprint(recovery)
|
||||
err := errors.New(recStr)
|
||||
go client.CaptureMessageAndWait(
|
||||
recStr,
|
||||
tags,
|
||||
raven.NewException(err, stacktrace),
|
||||
raven.NewHttp(c.Request),
|
||||
)
|
||||
}
|
||||
|
||||
for _, err := range c.Errors {
|
||||
if errorsStacktrace {
|
||||
stacktrace := NewRavenStackTrace(client, err.Err, 0)
|
||||
go client.CaptureMessageAndWait(
|
||||
err.Error(),
|
||||
tags,
|
||||
raven.NewException(err.Err, stacktrace),
|
||||
raven.NewHttp(c.Request),
|
||||
)
|
||||
} else {
|
||||
go client.CaptureErrorAndWait(err.Err, tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PanicLogger() ErrorHandlerFunc {
|
||||
return func(recovery interface{}, c *gin.Context) {
|
||||
if recovery != nil {
|
||||
fmt.Printf("===\n%+v\n", recovery)
|
||||
debug.PrintStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorLogger() ErrorHandlerFunc {
|
||||
return func(recovery interface{}, c *gin.Context) {
|
||||
for _, err := range c.Errors {
|
||||
fmt.Printf("===\n%+v\n", err.Err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewRavenStackTrace(client *raven.Client, myerr error, skip int) *raven.Stacktrace {
|
||||
st := getErrorStackTraceConverted(myerr, 3, client.IncludePaths())
|
||||
if st == nil {
|
||||
st = raven.NewStacktrace(skip, 3, client.IncludePaths())
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func getErrorStackTraceConverted(err error, context int, appPackagePrefixes []string) *raven.Stacktrace {
|
||||
st := getErrorCauseStackTrace(err)
|
||||
if st == nil {
|
||||
return nil
|
||||
}
|
||||
return convertStackTrace(st, context, appPackagePrefixes)
|
||||
}
|
||||
|
||||
func getErrorCauseStackTrace(err error) errors.StackTrace {
|
||||
// This code is inspired by github.com/pkg/errors.Cause().
|
||||
var st errors.StackTrace
|
||||
for err != nil {
|
||||
s := getErrorStackTrace(err)
|
||||
if s != nil {
|
||||
st = s
|
||||
}
|
||||
err = getErrorCause(err)
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func convertStackTrace(st errors.StackTrace, context int, appPackagePrefixes []string) *raven.Stacktrace {
|
||||
// This code is borrowed from github.com/getsentry/raven-go.NewStacktrace().
|
||||
var frames []*raven.StacktraceFrame
|
||||
for _, f := range st {
|
||||
frame := convertFrame(f, context, appPackagePrefixes)
|
||||
if frame != nil {
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
}
|
||||
if len(frames) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
|
||||
frames[i], frames[j] = frames[j], frames[i]
|
||||
}
|
||||
return &raven.Stacktrace{Frames: frames}
|
||||
}
|
||||
|
||||
func convertFrame(f errors.Frame, context int, appPackagePrefixes []string) *raven.StacktraceFrame {
|
||||
// This code is borrowed from github.com/pkg/errors.Frame.
|
||||
pc := uintptr(f) - 1
|
||||
fn := runtime.FuncForPC(pc)
|
||||
var file string
|
||||
var line int
|
||||
if fn != nil {
|
||||
file, line = fn.FileLine(pc)
|
||||
} else {
|
||||
file = "unknown"
|
||||
}
|
||||
return raven.NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
|
||||
}
|
||||
|
||||
func getErrorStackTrace(err error) errors.StackTrace {
|
||||
ster, ok := err.(interface {
|
||||
StackTrace() errors.StackTrace
|
||||
})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return ster.StackTrace()
|
||||
}
|
||||
|
||||
func getErrorCause(err error) error {
|
||||
cer, ok := err.(interface {
|
||||
Cause() error
|
||||
})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return cer.Cause()
|
||||
}
|
||||
|
||||
type errorResponse struct {
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
func NotFound(errors ...string) (int, interface{}) {
|
||||
return http.StatusNotFound, errorResponse{
|
||||
Errors: errors,
|
||||
}
|
||||
}
|
||||
|
||||
func BadRequest(errors ...string) (int, interface{}) {
|
||||
return http.StatusBadRequest, errorResponse{
|
||||
Errors: errors,
|
||||
}
|
||||
}
|
20
main.go
20
main.go
@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/op/go-logging"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Options struct
|
||||
@ -16,11 +18,19 @@ type Options struct {
|
||||
const transport = "mg-telegram"
|
||||
|
||||
var (
|
||||
config *TransportConfig
|
||||
orm *Orm
|
||||
logger *logging.Logger
|
||||
options Options
|
||||
parser = flags.NewParser(&options, flags.Default)
|
||||
config *TransportConfig
|
||||
orm *Orm
|
||||
logger *logging.Logger
|
||||
options Options
|
||||
parser = flags.NewParser(&options, flags.Default)
|
||||
tokenCounter uint32
|
||||
localizer *i18n.Localizer
|
||||
bundle = &i18n.Bundle{DefaultLanguage: language.English}
|
||||
matcher = language.NewMatcher([]language.Tag{
|
||||
language.English,
|
||||
language.Russian,
|
||||
language.Spanish,
|
||||
})
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -6,8 +6,8 @@ import "time"
|
||||
type Connection struct {
|
||||
ID int `gorm:"primary_key"`
|
||||
ClientID string `gorm:"client_id type:varchar(70);not null;unique" json:"clientId,omitempty"`
|
||||
APIKEY string `gorm:"api_key type:varchar(100);not null" json:"api_key,omitempty"`
|
||||
APIURL string `gorm:"api_url type:varchar(255);not null" json:"api_url,omitempty"`
|
||||
APIKEY string `gorm:"api_key type:varchar(100);not null" json:"api_key,omitempty" binding:"required"`
|
||||
APIURL string `gorm:"api_url type:varchar(255);not null" json:"api_url,omitempty" binding:"required,validatecrmurl"`
|
||||
MGURL string `gorm:"mg_url type:varchar(255);not null;" json:"mg_url,omitempty"`
|
||||
MGToken string `gorm:"mg_token type:varchar(100);not null;unique" json:"mg_token,omitempty"`
|
||||
CreatedAt time.Time
|
||||
|
470
routing.go
470
routing.go
@ -2,163 +2,62 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/retailcrm/api-client-go/v5"
|
||||
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
validPath = regexp.MustCompile(`^/(save|settings|telegram)/([a-zA-Z0-9-:_+]+)$`)
|
||||
localizer *i18n.Localizer
|
||||
bundle = &i18n.Bundle{DefaultLanguage: language.English}
|
||||
matcher = language.NewMatcher([]language.Tag{
|
||||
language.English,
|
||||
language.Russian,
|
||||
language.Spanish,
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
|
||||
files, err := ioutil.ReadDir("translate")
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if !f.IsDir() {
|
||||
bundle.MustLoadMessageFile("translate/" + f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setLocale(al string) {
|
||||
tag, _ := language.MatchStrings(matcher, al)
|
||||
localizer = i18n.NewLocalizer(bundle, tag.String())
|
||||
}
|
||||
|
||||
// Response struct
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func setWrapperRoutes() {
|
||||
http.HandleFunc("/", connectHandler)
|
||||
http.HandleFunc("/settings/", makeHandler(settingsHandler))
|
||||
http.HandleFunc("/save/", saveHandler)
|
||||
http.HandleFunc("/create/", createHandler)
|
||||
http.HandleFunc("/actions/activity", activityHandler)
|
||||
http.HandleFunc("/add-bot/", addBotHandler)
|
||||
http.HandleFunc("/delete-bot/", deleteBotHandler)
|
||||
}
|
||||
|
||||
func renderTemplate(w http.ResponseWriter, tmpl string, c interface{}) {
|
||||
tm, err := template.ParseFiles("templates/layout.html", "templates/"+tmpl+".html")
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = tm.Execute(w, &c)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
m := validPath.FindStringSubmatch(r.URL.Path)
|
||||
if m == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
raven.CapturePanic(func() {
|
||||
fn(w, r, m[2])
|
||||
}, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func connectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
|
||||
account := r.URL.Query()
|
||||
func connectHandler(c *gin.Context) {
|
||||
rx := regexp.MustCompile(`/+$`)
|
||||
ra := rx.ReplaceAllString(account.Get("account"), ``)
|
||||
ra := rx.ReplaceAllString(c.Query("account"), ``)
|
||||
p := Connection{
|
||||
APIURL: ra,
|
||||
}
|
||||
|
||||
res := struct {
|
||||
Conn *Connection
|
||||
Locale map[string]interface{}
|
||||
Locale map[string]string
|
||||
}{
|
||||
&p,
|
||||
map[string]interface{}{
|
||||
"ButtonSave": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "button_save"}),
|
||||
"ApiKey": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "api_key"}),
|
||||
"Title": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "title"}),
|
||||
},
|
||||
getLocale(),
|
||||
}
|
||||
renderTemplate(w, "home", &res)
|
||||
|
||||
c.HTML(http.StatusOK, "home", &res)
|
||||
}
|
||||
|
||||
func addBotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
||||
logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
func addBotHandler(c *gin.Context) {
|
||||
var b Bot
|
||||
|
||||
err = json.Unmarshal(body, &b)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
||||
logger.Error(err.Error())
|
||||
if err := c.ShouldBindJSON(&b); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if b.Token == "" {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "no_bot_token"}), http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("no_bot_token")})
|
||||
return
|
||||
}
|
||||
|
||||
cl, err := getBotByToken(b.Token)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
||||
logger.Error(err.Error())
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if cl.ID != 0 {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "bot_already_created"}), http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("bot_already_created")})
|
||||
return
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(b.Token)
|
||||
if err != nil {
|
||||
logger.Error(b.Token, err.Error())
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_token"}), http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("incorrect_token")})
|
||||
return
|
||||
}
|
||||
|
||||
@ -167,17 +66,17 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
wr, err := bot.SetWebhook(tgbotapi.NewWebhook("https://" + config.HTTPServer.Host + "/telegram/" + bot.Token))
|
||||
if err != nil {
|
||||
logger.Error(b.Token, err.Error())
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_webhook"}), http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("error_creating_webhook")})
|
||||
return
|
||||
}
|
||||
|
||||
if !wr.Ok {
|
||||
logger.Error(b.Token, wr.ErrorCode, wr.Result)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_webhook"}), http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("error_creating_webhook")})
|
||||
return
|
||||
}
|
||||
|
||||
b.Name = GetBotName(bot)
|
||||
b.Name = bot.Self.FirstName
|
||||
|
||||
ch := v1.Channel{
|
||||
Type: "telegram",
|
||||
@ -189,92 +88,72 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
}
|
||||
|
||||
c := getConnectionById(b.ConnectionID)
|
||||
conn := getConnectionById(b.ConnectionID)
|
||||
|
||||
var client = v1.New(c.MGURL, c.MGToken)
|
||||
var client = v1.New(conn.MGURL, conn.MGToken)
|
||||
data, status, err := client.ActivateTransportChannel(ch)
|
||||
if status != http.StatusCreated {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activating_channel"}), http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, status, err.Error(), data)
|
||||
logger.Error(conn.APIURL, status, err.Error(), data)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("error_activating_channel")})
|
||||
return
|
||||
}
|
||||
|
||||
b.Channel = data.ChannelID
|
||||
|
||||
err = c.createBot(b)
|
||||
err = conn.createBot(b)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonString, err := json.Marshal(b)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(jsonString)
|
||||
c.JSON(http.StatusCreated, jsonString)
|
||||
}
|
||||
|
||||
func deleteBotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
func deleteBotHandler(c *gin.Context) {
|
||||
var b Bot
|
||||
|
||||
err = json.Unmarshal(body, &b)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
logger.Error(err.Error())
|
||||
if err := c.ShouldBindJSON(&b); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
c := getConnectionById(b.ConnectionID)
|
||||
if c.MGURL == "" || c.MGToken == "" {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest)
|
||||
conn := getConnectionById(b.ConnectionID)
|
||||
if conn.MGURL == "" || conn.MGToken == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("not_found_account")})
|
||||
logger.Error(b.ID, "MGURL or MGToken is empty")
|
||||
return
|
||||
}
|
||||
|
||||
var client = v1.New(c.MGURL, c.MGToken)
|
||||
var client = v1.New(conn.MGURL, conn.MGToken)
|
||||
|
||||
data, status, err := client.DeactivateTransportChannel(getBotChannelByToken(b.Token))
|
||||
if status > http.StatusOK {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_deactivating_channel"}), http.StatusBadRequest)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("error_deactivating_channel")})
|
||||
logger.Error(b.ID, status, err.Error(), data)
|
||||
return
|
||||
}
|
||||
|
||||
err = b.deleteBot()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
logger.Error(b.ID, err.Error())
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) {
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
func settingsHandler(c *gin.Context) {
|
||||
uid := c.Param("uid")
|
||||
|
||||
p := getConnection(uid)
|
||||
if p.ID == 0 {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
|
||||
@ -283,111 +162,58 @@ func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) {
|
||||
res := struct {
|
||||
Conn *Connection
|
||||
Bots Bots
|
||||
Locale map[string]interface{}
|
||||
Locale map[string]string
|
||||
}{
|
||||
p,
|
||||
bots,
|
||||
map[string]interface{}{
|
||||
"ButtonSave": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "button_save"}),
|
||||
"ApiKey": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "api_key"}),
|
||||
"TabSettings": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "tab_settings"}),
|
||||
"TabBots": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "tab_bots"}),
|
||||
"TableName": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_name"}),
|
||||
"TableToken": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_token"}),
|
||||
"AddBot": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "add_bot"}),
|
||||
"TableDelete": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_delete"}),
|
||||
"Title": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "title"}),
|
||||
},
|
||||
getLocale(),
|
||||
}
|
||||
|
||||
renderTemplate(w, "form", res)
|
||||
c.HTML(http.StatusOK, "form", &res)
|
||||
}
|
||||
|
||||
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
func saveHandler(c *gin.Context) {
|
||||
var conn Connection
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
if err := c.BindJSON(&conn); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("incorrect_url_key")})
|
||||
return
|
||||
}
|
||||
|
||||
var c Connection
|
||||
|
||||
err = json.Unmarshal(body, &c)
|
||||
_, err, code := getAPIClient(conn.APIURL, conn.APIKEY)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
c.AbortWithStatusJSON(code, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
err = validateCrmSettings(c)
|
||||
err = conn.saveConnection()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err, code := getAPIClient(c.APIURL, c.APIKEY)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.saveConnection()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"})))
|
||||
c.JSON(http.StatusOK, gin.H{"message": getLocalizedMessage("successful")})
|
||||
}
|
||||
|
||||
func createHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
func createHandler(c *gin.Context) {
|
||||
var conn Connection
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
logger.Error(err.Error())
|
||||
if err := c.BindJSON(&conn); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("incorrect_url_key")})
|
||||
return
|
||||
}
|
||||
|
||||
var c Connection
|
||||
conn.ClientID = GenerateToken()
|
||||
|
||||
err = json.Unmarshal(body, &c)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ClientID = GenerateToken()
|
||||
|
||||
err = validateCrmSettings(c)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cl := getConnectionByURL(c.APIURL)
|
||||
cl := getConnectionByURL(conn.APIURL)
|
||||
if cl.ID != 0 {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "connection_already_created"}), http.StatusBadRequest)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("connection_already_created")})
|
||||
return
|
||||
}
|
||||
|
||||
client, err, code := getAPIClient(c.APIURL, c.APIKEY)
|
||||
client, err, _ := getAPIClient(conn.APIURL, conn.APIKEY)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), code)
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@ -396,7 +222,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) {
|
||||
IntegrationCode: transport,
|
||||
Active: true,
|
||||
Name: "Telegram",
|
||||
ClientID: c.ClientID,
|
||||
ClientID: conn.ClientID,
|
||||
Logo: fmt.Sprintf(
|
||||
"https://%s/static/telegram_logo.svg",
|
||||
config.HTTPServer.Host,
|
||||
@ -408,7 +234,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) {
|
||||
AccountURL: fmt.Sprintf(
|
||||
"https://%s/settings/%s",
|
||||
config.HTTPServer.Host,
|
||||
c.ClientID,
|
||||
conn.ClientID,
|
||||
),
|
||||
Actions: map[string]string{"activity": "/actions/activity"},
|
||||
Integrations: &v5.Integrations{
|
||||
@ -423,172 +249,60 @@ func createHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
data, status, errr := client.IntegrationModuleEdit(integration)
|
||||
if errr.RuntimeErr != nil {
|
||||
raven.CaptureErrorAndWait(errr.RuntimeErr, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_integration"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, status, errr.RuntimeErr, data)
|
||||
c.Error(errr.RuntimeErr)
|
||||
return
|
||||
}
|
||||
|
||||
if status >= http.StatusBadRequest {
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activity_mg"}), http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, status, errr.ApiErr, data)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": getLocalizedMessage("error_activity_mg")})
|
||||
logger.Error(conn.APIURL, status, errr.ApiErr, data)
|
||||
return
|
||||
}
|
||||
|
||||
c.MGURL = data.Info["baseUrl"]
|
||||
c.MGToken = data.Info["token"]
|
||||
c.Active = true
|
||||
conn.MGURL = data.Info["baseUrl"]
|
||||
conn.MGToken = data.Info["token"]
|
||||
conn.Active = true
|
||||
|
||||
err = c.createConnection()
|
||||
err = conn.createConnection()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
res := struct {
|
||||
Url string
|
||||
Message string
|
||||
}{
|
||||
Url: "/settings/" + c.ClientID,
|
||||
Message: localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"}),
|
||||
}
|
||||
|
||||
jss, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusFound)
|
||||
w.Write(jss)
|
||||
c.JSON(
|
||||
http.StatusCreated,
|
||||
gin.H{
|
||||
"url": "/settings/" + conn.ClientID,
|
||||
"message": getLocalizedMessage("successful"),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func activityHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
setLocale(r.Header.Get("Accept-Language"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
res := Response{Success: false}
|
||||
func activityHandler(c *gin.Context) {
|
||||
var rec v5.ActivityCallback
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "set_method"})
|
||||
jsonString, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
w.Write(jsonString)
|
||||
if err := c.ShouldBindJSON(&rec); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
var rec v5.Activity
|
||||
|
||||
err := json.Unmarshal([]byte(r.FormValue("activity")), &rec)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "wrong_data"})
|
||||
jsonString, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
w.Write(jsonString)
|
||||
conn := getConnection(rec.ClientId)
|
||||
if conn.ID == 0 {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest,
|
||||
gin.H{
|
||||
"success": false,
|
||||
"error": "Wrong data",
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
c := getConnection(r.FormValue("clientId"))
|
||||
c.Active = rec.Active && !rec.Freeze
|
||||
conn.Active = rec.Activity.Active && !rec.Activity.Freeze
|
||||
|
||||
if err := c.setConnectionActivity(); err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "wrong_data"})
|
||||
jsonString, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
w.Write(jsonString)
|
||||
if err := conn.setConnectionActivity(); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
res.Success = true
|
||||
jsonString, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(jsonString)
|
||||
}
|
||||
|
||||
func validateCrmSettings(c Connection) error {
|
||||
if c.APIURL == "" || c.APIKEY == "" {
|
||||
return errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "missing_url_key"}))
|
||||
}
|
||||
|
||||
if res, _ := regexp.MatchString(`https://?[\da-z\.-]+\.(retailcrm\.(ru|pro)|ecomlogic\.com)`, c.APIURL); !res {
|
||||
return errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_url"}))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAPIClient(url, key string) (*v5.Client, error, int) {
|
||||
client := v5.New(url, key)
|
||||
|
||||
cr, status, errr := client.APICredentials()
|
||||
if errr.RuntimeErr != nil {
|
||||
raven.CaptureErrorAndWait(errr.RuntimeErr, nil)
|
||||
logger.Error(url, status, errr.RuntimeErr, cr)
|
||||
return nil, errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"})), http.StatusInternalServerError
|
||||
|
||||
}
|
||||
|
||||
if !cr.Success {
|
||||
logger.Error(url, status, errr.ApiErr, cr)
|
||||
return nil, errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_url_key"})), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if res := checkCredentials(cr.Credentials); len(res) != 0 {
|
||||
logger.Error(url, status, res)
|
||||
return nil,
|
||||
errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||
MessageID: "missing_credentials",
|
||||
TemplateData: map[string]interface{}{
|
||||
"Credentials": strings.Join(res, ", "),
|
||||
},
|
||||
})),
|
||||
http.StatusBadRequest
|
||||
}
|
||||
|
||||
return client, nil, 0
|
||||
}
|
||||
|
||||
func checkCredentials(credential []string) []string {
|
||||
rc := []string{
|
||||
"/api/integration-modules/{code}",
|
||||
"/api/integration-modules/{code}/edit",
|
||||
}
|
||||
|
||||
for kn, vn := range rc {
|
||||
for _, vc := range credential {
|
||||
if vn == vc {
|
||||
if len(rc) == 1 {
|
||||
rc = rc[:0]
|
||||
break
|
||||
}
|
||||
rc = append(rc[:kn], rc[kn+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
88
run.go
88
run.go
@ -1,15 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-contrib/multitemplate"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
_ "github.com/golang-migrate/migrate/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/source/file"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -28,7 +33,6 @@ func (x *RunCommand) Execute(args []string) error {
|
||||
config = LoadConfig(options.Config)
|
||||
orm = NewDb(config)
|
||||
logger = newLogger()
|
||||
raven.SetDSN(config.SentryDSN)
|
||||
|
||||
go start()
|
||||
|
||||
@ -47,8 +51,78 @@ func (x *RunCommand) Execute(args []string) error {
|
||||
}
|
||||
|
||||
func start() {
|
||||
setWrapperRoutes()
|
||||
setTransportRoutes()
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
http.ListenAndServe(config.HTTPServer.Listen, nil)
|
||||
routing := setup()
|
||||
routing.Run(config.HTTPServer.Listen)
|
||||
}
|
||||
|
||||
func setup() *gin.Engine {
|
||||
loadTranslateFile()
|
||||
|
||||
binding.Validator = new(defaultValidator)
|
||||
|
||||
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||
v.RegisterValidation("validatecrmurl", validateCrmURL)
|
||||
}
|
||||
|
||||
if config.Debug == false {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
if config.Debug {
|
||||
r.Use(gin.Logger())
|
||||
}
|
||||
|
||||
r.Static("/static", "./static")
|
||||
r.HTMLRender = createHTMLRender()
|
||||
|
||||
r.Use(func(c *gin.Context) {
|
||||
setLocale(c.GetHeader("Accept-Language"))
|
||||
})
|
||||
|
||||
errorHandlers := []ErrorHandlerFunc{
|
||||
PanicLogger(),
|
||||
ErrorLogger(),
|
||||
ErrorResponseHandler(),
|
||||
}
|
||||
sentry, _ := raven.New(config.SentryDSN)
|
||||
|
||||
if sentry != nil {
|
||||
errorHandlers = append(errorHandlers, ErrorCaptureHandler(sentry, false))
|
||||
}
|
||||
|
||||
r.Use(ErrorHandler(errorHandlers...))
|
||||
|
||||
r.GET("/", connectHandler)
|
||||
r.GET("/settings/:uid", settingsHandler)
|
||||
r.POST("/save/", saveHandler)
|
||||
r.POST("/create/", createHandler)
|
||||
r.POST("/add-bot/", addBotHandler)
|
||||
r.POST("/delete-bot/", deleteBotHandler)
|
||||
r.POST("/actions/activity", activityHandler)
|
||||
r.POST("/telegram/:token", telegramWebhookHandler)
|
||||
r.POST("/webhook/", mgWebhookHandler)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func createHTMLRender() multitemplate.Renderer {
|
||||
r := multitemplate.NewRenderer()
|
||||
r.AddFromFiles("home", "templates/layout.html", "templates/home.html")
|
||||
r.AddFromFiles("form", "templates/layout.html", "templates/form.html")
|
||||
return r
|
||||
}
|
||||
|
||||
func loadTranslateFile() {
|
||||
bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
|
||||
files, err := ioutil.ReadDir("translate")
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if !f.IsDir() {
|
||||
bundle.MustLoadMessageFile("translate/" + f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,12 @@ $('#save-crm').on("submit", function(e) {
|
||||
send(
|
||||
$(this).attr('action'),
|
||||
formDataToObj($(this).serializeArray()),
|
||||
function () {
|
||||
return 0;
|
||||
function (data) {
|
||||
sessionStorage.setItem("createdMsg", data.message);
|
||||
|
||||
document.location.replace(
|
||||
location.protocol.concat("//").concat(window.location.host) + data.url
|
||||
);
|
||||
}
|
||||
)
|
||||
});
|
||||
@ -15,7 +19,7 @@ $("#save").on("submit", function(e) {
|
||||
$(this).attr('action'),
|
||||
formDataToObj($(this).serializeArray()),
|
||||
function (data) {
|
||||
M.toast({html: data});
|
||||
M.toast({html: data.message});
|
||||
}
|
||||
)
|
||||
});
|
||||
@ -62,17 +66,8 @@ function send(url, data, callback) {
|
||||
type: "POST",
|
||||
success: callback,
|
||||
error: function (res){
|
||||
if (res.status < 400) {
|
||||
if (res.responseText) {
|
||||
let resObj = JSON.parse(res.responseText);
|
||||
sessionStorage.setItem("createdMsg", resObj.Message);
|
||||
|
||||
document.location.replace(
|
||||
location.protocol.concat("//").concat(window.location.host) + resObj.Url
|
||||
);
|
||||
}
|
||||
} else {
|
||||
M.toast({html: res.responseText})
|
||||
if (res.status >= 400) {
|
||||
M.toast({html: res.responseJSON.error})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
154
telegram.go
154
telegram.go
@ -1,10 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -13,73 +11,46 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||
)
|
||||
|
||||
func setTransportRoutes() {
|
||||
http.HandleFunc("/telegram/", makeHandler(telegramWebhookHandler))
|
||||
http.HandleFunc("/webhook/", mgWebhookHandler)
|
||||
}
|
||||
|
||||
// GetBotName function
|
||||
func GetBotName(bot *tgbotapi.BotAPI) string {
|
||||
return bot.Self.FirstName
|
||||
}
|
||||
|
||||
func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string) {
|
||||
defer r.Body.Close()
|
||||
func telegramWebhookHandler(c *gin.Context) {
|
||||
token := c.Param("token")
|
||||
b, err := getBotByToken(token)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(token, err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if b.ID == 0 {
|
||||
logger.Error(token, "telegramWebhookHandler: missing or deactivated")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
c.AbortWithStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
c := getConnectionById(b.ConnectionID)
|
||||
if !c.Active {
|
||||
logger.Error(c.ClientID, "telegramWebhookHandler: connection deactivated")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
conn := getConnectionById(b.ConnectionID)
|
||||
if !conn.Active {
|
||||
c.AbortWithStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
var update tgbotapi.Update
|
||||
|
||||
bytes, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(token, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
if err := c.ShouldBindJSON(&update); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Debug {
|
||||
logger.Debugf("telegramWebhookHandler: %v", string(bytes))
|
||||
logger.Debugf("mgWebhookHandler request: %v", update)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bytes, &update)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(token, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var client = v1.New(c.MGURL, c.MGToken)
|
||||
var client = v1.New(conn.MGURL, conn.MGToken)
|
||||
|
||||
if update.Message != nil {
|
||||
if update.Message.Text == "" {
|
||||
setLocale(update.Message.From.LanguageCode)
|
||||
update.Message.Text = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: getMessageID(update.Message)})
|
||||
update.Message.Text = getLocalizedMessage(getMessageID(update.Message))
|
||||
}
|
||||
|
||||
nickname := update.Message.From.UserName
|
||||
@ -92,18 +63,14 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
|
||||
if user.Expired(config.UpdateInterval) || user.ID == 0 {
|
||||
fileID, fileURL, err := GetFileIDAndURL(b.Token, update.Message.From.ID)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if fileID != user.UserPhotoID && fileURL != "" {
|
||||
picURL, err := UploadUserAvatar(fileURL)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -117,9 +84,7 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
|
||||
|
||||
err = user.save()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -161,9 +126,8 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
|
||||
|
||||
data, st, err := client.Messages(snd)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(token, err.Error(), st, data)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -175,7 +139,7 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
|
||||
if update.EditedMessage != nil {
|
||||
if update.EditedMessage.Text == "" {
|
||||
setLocale(update.EditedMessage.From.LanguageCode)
|
||||
update.EditedMessage.Text = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: getMessageID(update.Message)})
|
||||
update.EditedMessage.Text = getLocalizedMessage(getMessageID(update.Message))
|
||||
}
|
||||
|
||||
snd := v1.UpdateData{
|
||||
@ -191,9 +155,8 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
|
||||
|
||||
data, st, err := client.UpdateMessages(snd)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(token, err.Error(), st, data)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -202,63 +165,47 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
c.AbortWithStatus(http.StatusOK)
|
||||
}
|
||||
|
||||
func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
clientID := r.Header.Get("Clientid")
|
||||
func mgWebhookHandler(c *gin.Context) {
|
||||
clientID := c.GetHeader("Clientid")
|
||||
if clientID == "" {
|
||||
logger.Error("mgWebhookHandler clientID is empty")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
c := getConnection(clientID)
|
||||
if !c.Active {
|
||||
logger.Error(c.ClientID, "mgWebhookHandler: connection deactivated")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Connection deactivated"))
|
||||
conn := getConnection(clientID)
|
||||
if !conn.Active {
|
||||
logger.Error(conn.ClientID, "mgWebhookHandler: connection deactivated")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Connection deactivated"})
|
||||
return
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
var msg v1.WebhookRequest
|
||||
if err := c.ShouldBindJSON(&msg); err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Debug {
|
||||
logger.Debugf("mgWebhookHandler request: %v", string(bytes))
|
||||
}
|
||||
|
||||
var msg v1.WebhookRequest
|
||||
err = json.Unmarshal(bytes, &msg)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
logger.Debugf("mgWebhookHandler request: %v", msg)
|
||||
}
|
||||
|
||||
uid, _ := strconv.Atoi(msg.Data.ExternalMessageID)
|
||||
cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64)
|
||||
|
||||
b := getBot(c.ID, msg.Data.ChannelID)
|
||||
b := getBot(conn.ID, msg.Data.ChannelID)
|
||||
if b.ID == 0 {
|
||||
logger.Error(msg.Data.ChannelID, "mgWebhookHandler: missing or deactivated")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("missing or deactivated"))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing or deactivated"})
|
||||
return
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(b.Token)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -268,9 +215,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if msg.Data.QuoteExternalID != "" {
|
||||
qid, err := strconv.Atoi(msg.Data.QuoteExternalID)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
m.ReplyToMessageID = qid
|
||||
@ -278,9 +223,8 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
msg, err := bot.Send(m)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -288,27 +232,14 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debugf("mgWebhookHandler sent %v", msg)
|
||||
}
|
||||
|
||||
rsp, err := json.Marshal(map[string]string{"external_message_id": strconv.Itoa(msg.MessageID)})
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Debug {
|
||||
logger.Debugf("mgWebhookHandler sent response %v", string(rsp))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(rsp)
|
||||
c.JSON(http.StatusOK, gin.H{"external_message_id": strconv.Itoa(msg.MessageID)})
|
||||
}
|
||||
|
||||
if msg.Type == "message_updated" {
|
||||
msg, err := bot.Send(tgbotapi.NewEditMessageText(cid, uid, msg.Data.Content))
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -316,16 +247,14 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debugf("mgWebhookHandler update %v", msg)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Message updated"))
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Message updated"})
|
||||
}
|
||||
|
||||
if msg.Type == "message_deleted" {
|
||||
msg, err := bot.Send(tgbotapi.NewDeleteMessage(cid, uid))
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
logger.Error(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -333,8 +262,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Debugf("mgWebhookHandler delete %v", msg)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Message deleted"))
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Message deleted"})
|
||||
}
|
||||
}
|
||||
|
||||
|
17
token.go
17
token.go
@ -1,17 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tokenCounter uint32
|
||||
|
||||
// GenerateToken function
|
||||
func GenerateToken() string {
|
||||
c := atomic.AddUint32(&tokenCounter, 1)
|
||||
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%d", time.Now().UnixNano(), c))))
|
||||
}
|
93
utils.go
Normal file
93
utils.go
Normal file
@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/retailcrm/api-client-go/v5"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func setLocale(al string) {
|
||||
tag, _ := language.MatchStrings(matcher, al)
|
||||
localizer = i18n.NewLocalizer(bundle, tag.String())
|
||||
}
|
||||
|
||||
func getLocale() map[string]string {
|
||||
return map[string]string{
|
||||
"ButtonSave": getLocalizedMessage("button_save"),
|
||||
"ApiKey": getLocalizedMessage("api_key"),
|
||||
"TabSettings": getLocalizedMessage("tab_settings"),
|
||||
"TabBots": getLocalizedMessage("tab_bots"),
|
||||
"TableName": getLocalizedMessage("table_name"),
|
||||
"TableToken": getLocalizedMessage("table_token"),
|
||||
"AddBot": getLocalizedMessage("add_bot"),
|
||||
"TableDelete": getLocalizedMessage("table_delete"),
|
||||
"Title": getLocalizedMessage("title"),
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalizedMessage(messageID string) string {
|
||||
return localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
|
||||
}
|
||||
|
||||
// GenerateToken function
|
||||
func GenerateToken() string {
|
||||
c := atomic.AddUint32(&tokenCounter, 1)
|
||||
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%d", time.Now().UnixNano(), c))))
|
||||
}
|
||||
|
||||
func getAPIClient(url, key string) (*v5.Client, error, int) {
|
||||
client := v5.New(url, key)
|
||||
|
||||
cr, status, e := client.APICredentials()
|
||||
if e.RuntimeErr != nil {
|
||||
logger.Error(url, status, e.RuntimeErr, cr)
|
||||
return nil, errors.New(getLocalizedMessage("not_found_account")), http.StatusInternalServerError
|
||||
|
||||
}
|
||||
|
||||
if !cr.Success {
|
||||
logger.Error(url, status, e.ApiErr, cr)
|
||||
return nil, errors.New(getLocalizedMessage("incorrect_url_key")), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if res := checkCredentials(cr.Credentials); len(res) != 0 {
|
||||
logger.Error(url, status, res)
|
||||
return nil,
|
||||
errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||
MessageID: "missing_credentials",
|
||||
TemplateData: map[string]interface{}{
|
||||
"Credentials": strings.Join(res, ", "),
|
||||
},
|
||||
})),
|
||||
http.StatusBadRequest
|
||||
}
|
||||
|
||||
return client, nil, 0
|
||||
}
|
||||
|
||||
func checkCredentials(credential []string) []string {
|
||||
rc := config.Credentials
|
||||
|
||||
for _, vc := range credential {
|
||||
for kn, vn := range rc {
|
||||
if vn == vc {
|
||||
if len(rc) == 1 {
|
||||
rc = rc[:0]
|
||||
break
|
||||
}
|
||||
rc = append(rc[:kn], rc[kn+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc
|
||||
}
|
57
validator.go
Normal file
57
validator.go
Normal file
@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
type defaultValidator struct {
|
||||
once sync.Once
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
var _ binding.StructValidator = &defaultValidator{}
|
||||
|
||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||
|
||||
if kindOfData(obj) == reflect.Struct {
|
||||
v.lazyinit()
|
||||
if err := v.validate.Struct(obj); err != nil {
|
||||
return error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *defaultValidator) Engine() interface{} {
|
||||
v.lazyinit()
|
||||
return v.validate
|
||||
}
|
||||
|
||||
func (v *defaultValidator) lazyinit() {
|
||||
v.once.Do(func() {
|
||||
v.validate = validator.New()
|
||||
v.validate.SetTagName("binding")
|
||||
})
|
||||
}
|
||||
|
||||
func kindOfData(data interface{}) reflect.Kind {
|
||||
value := reflect.ValueOf(data)
|
||||
valueType := value.Kind()
|
||||
|
||||
if valueType == reflect.Ptr {
|
||||
valueType = value.Elem().Kind()
|
||||
}
|
||||
return valueType
|
||||
}
|
||||
|
||||
func validateCrmURL(field validator.FieldLevel) bool {
|
||||
regCommandName := regexp.MustCompile(`https://?[\da-z.-]+\.(retailcrm\.(ru|pro)|ecomlogic\.com)`)
|
||||
|
||||
return regCommandName.Match([]byte(field.Field().Interface().(string)))
|
||||
}
|
Loading…
Reference in New Issue
Block a user