1
0
mirror of synced 2024-11-21 20:16:02 +03:00

add gin, errors handler and minor fixes

This commit is contained in:
DmitryZagorulko 2018-08-14 17:49:01 +03:00
parent a60209c036
commit 747b73ff87
14 changed files with 610 additions and 538 deletions

View File

@ -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

View File

@ -20,3 +20,7 @@ config_aws:
bucket: ~
folder_name: ~
content_type: image/jpeg
credentials:
- "/api/integration-modules/{code}"
- "/api/integration-modules/{code}/edit"

View File

@ -20,3 +20,7 @@ config_aws:
bucket: ~
folder_name: ~
content_type: image/jpeg
credentials:
- "/api/integration-modules/{code}"
- "/api/integration-modules/{code}/edit"

View File

@ -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
View 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
View File

@ -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() {

View File

@ -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

View File

@ -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
View File

@ -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())
}
}
}

View File

@ -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})
}
}
});

View File

@ -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"})
}
}

View File

@ -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
View 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
View 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)))
}