diff --git a/migrations/1527515265_app.down.sql b/migrations/1527515265_app.down.sql index 0481bb7..b83d4fb 100644 --- a/migrations/1527515265_app.down.sql +++ b/migrations/1527515265_app.down.sql @@ -1,26 +1,32 @@ alter table connection - drop constraint connection_client_id_key, - drop constraint connection_api_key_key, - drop constraint connection_api_url_key, - drop constraint connection_mg_url_key; -alter table connection + drop constraint connection_key, alter column client_id drop not null, alter column api_key drop not null, - alter column api_url drop not null; + alter column api_url drop not null, + alter column mg_url drop not null, + alter column mg_token drop not null, + alter column api_url type varchar(100), + alter column mg_url type varchar(100); alter table bot - alter column connection_id type varchar(70), + add column client_id varchar(70); + +update bot b + set client_id = c.client_id + from connection c + where b.connection_id = c.id; + +alter table bot + drop column connection_id, + alter column channel drop not null, alter column token drop not null, - drop constraint bot_token_key; - -alter table bot - rename column connection_id to client_id; + drop constraint bot_key; create table mapping ( - id serial not null + id serial not null constraint mapping_pkey primary key, site_code text, bot_id text -); \ No newline at end of file +); diff --git a/migrations/1527515265_app.up.sql b/migrations/1527515265_app.up.sql index 916f498..d0a94f7 100644 --- a/migrations/1527515265_app.up.sql +++ b/migrations/1527515265_app.up.sql @@ -1,19 +1,27 @@ alter table connection - add constraint connection_client_id_key unique (api_key), - add constraint connection_api_key_key unique (api_url), - add constraint connection_api_url_key unique (mg_url), - add constraint connection_mg_url_key unique (mg_token); -alter table connection + add constraint connection_key unique (client_id, mg_token), alter column client_id set not null, alter column api_key set not null, - alter column api_url set not null; + alter column api_url set not null, + alter column mg_url set not null, + alter column mg_token set not null, + alter column api_url type varchar(255), + alter column mg_url type varchar(255); alter table bot - alter column client_id type integer using client_id::integer, + add column connection_id integer; + +update bot b + set connection_id = c.id + from connection c + where b.client_id = c.client_id; + +alter table bot + drop column client_id, + alter column channel set not null, alter column token set not null, - add constraint bot_token_key unique (token); + add constraint bot_key unique (channel, token); -alter table bot - rename column client_id to connection_id; +alter table bot add foreign key (connection_id) references connection on delete cascade; -drop table mapping; \ No newline at end of file +drop table mapping; diff --git a/models.go b/models.go index 0187538..aaa5236 100644 --- a/models.go +++ b/models.go @@ -6,10 +6,10 @@ 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;unique" json:"api_key,omitempty"` - APIURL string `gorm:"api_url type:varchar(100);not null;unique" json:"api_url,omitempty"` - MGURL string `gorm:"mg_url type:varchar(100);unique" json:"mg_url,omitempty"` - MGToken string `gorm:"mg_token type:varchar(100)" json:"mg_token,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"` + 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 UpdatedAt time.Time Active bool `json:"active,omitempty"` @@ -20,7 +20,7 @@ type Connection struct { type Bot struct { ID int `gorm:"primary_key"` ConnectionID int `gorm:"connection_id" json:"connectionId,omitempty"` - Channel uint64 `json:"channel,omitempty"` + Channel uint64 `gorm:"channel;not null;unique" json:"channel,omitempty"` Token string `gorm:"token type:varchar(100);not null;unique" json:"token,omitempty"` Name string `gorm:"name type:varchar(40)" json:"name,omitempty"` CreatedAt time.Time diff --git a/repository.go b/repository.go index 2da630c..1492e24 100644 --- a/repository.go +++ b/repository.go @@ -1,5 +1,7 @@ package main +import "github.com/jinzhu/gorm" + func getConnection(uid string) *Connection { var connection Connection orm.DB.First(&connection, "client_id = ?", uid) @@ -30,11 +32,18 @@ func (c *Connection) createBot(b Bot) error { return orm.DB.Model(c).Association("Bots").Append(&b).Error } -func getBotByToken(token string) *Bot { - var bot Bot - orm.DB.First(&bot, "token = ?", token) +func getConnectionByBotToken(token string) (*Connection, error) { + var c Connection + err := orm.DB.Where("active = ?", true). + Preload("Bots", "token = ?", token). + First(&c).Error + if gorm.IsRecordNotFoundError(err) { + return &c, nil + } else { + return &c, err + } - return &bot + return &c, nil } func getBotByChannel(ch uint64) *Bot { @@ -44,10 +53,6 @@ func getBotByChannel(ch uint64) *Bot { return &bot } -func (b *Bot) createBot() error { - return orm.DB.Create(b).Error -} - func (b *Bot) setBotActivity() error { return orm.DB.Model(b).Where("token = ?", b.Token).Update("Active", !b.Active).Error } @@ -59,9 +64,14 @@ func getBotChannelByToken(token string) uint64 { return b.Channel } -func (b *Bots) getBotsByClientID(uid string) error { - var c Connection - return orm.DB.First(&c, "client_id = ?", uid).Association("Bots").Find(b).Error +func (c Connection) getBotsByClientID() Bots { + var b Bots + err := orm.DB.Model(c).Association("Bots").Find(&b).Error + if err != nil { + logger.Error(err) + } + + return b } func getConnectionById(id int) *Connection { diff --git a/routing.go b/routing.go index 06b3900..edc4a8d 100644 --- a/routing.go +++ b/routing.go @@ -133,15 +133,15 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { return } - c := getConnectionById(b.ConnectionID) - if c.MGURL == "" || c.MGToken == "" { - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest) - logger.Error(b.Token, "MGURL or MGToken is empty") + cb, err := getConnectionByBotToken(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()) return } - cl := getBotByToken(b.Token) - if cl.ID != 0 { + if len(cb.Bots) != 0 { http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "bot_already_created"}), http.StatusBadRequest) return } @@ -180,22 +180,22 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { }, } - var client = v1.New(c.MGURL, c.MGToken) + var client = v1.New(cb.MGURL, cb.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(cb.APIURL, status, err.Error(), data) return } b.Channel = data.ChannelID b.Active = true - err = c.createBot(b) + err = cb.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()) + logger.Error(cb.APIURL, err.Error()) return } @@ -203,7 +203,7 @@ func addBotHandler(w http.ResponseWriter, r *http.Request) { 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()) + logger.Error(cb.APIURL, err.Error()) return } @@ -286,13 +286,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) { return } - bots := Bots{} - err := bots.getBotsByClientID(uid) - if err != nil { - raven.CaptureErrorAndWait(err, nil) - http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError) - return - } + bots := p.getBotsByClientID() res := struct { Conn *Connection @@ -368,6 +362,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) { if err != nil { raven.CaptureErrorAndWait(err, nil) http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError) + logger.Error(err.Error()) return } @@ -377,6 +372,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) { 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 } @@ -453,6 +449,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) { 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 } @@ -468,6 +465,7 @@ func createHandler(w http.ResponseWriter, r *http.Request) { 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 } diff --git a/routing_test.go b/routing_test.go index d1fc9df..ef1bb12 100644 --- a/routing_test.go +++ b/routing_test.go @@ -1,13 +1,20 @@ package main import ( + "encoding/json" + "fmt" + "io/ioutil" "net/http" "net/http/httptest" "net/url" + "sort" "strings" "testing" "github.com/h2non/gock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" ) func init() { @@ -26,7 +33,9 @@ func init() { } c.createConnection() + orm.DB.Where("token = 123123:Qwerty").Delete(Bot{}) } + func TestRouting_connectHandler(t *testing.T) { req, err := http.NewRequest("GET", "/", nil) if err != nil { @@ -37,11 +46,8 @@ func TestRouting_connectHandler(t *testing.T) { handler := http.HandlerFunc(connectHandler) handler.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - rr.Code, http.StatusOK) - } + assert.Equal(t, http.StatusOK, rr.Code, + fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)) } func TestRouting_addBotHandler(t *testing.T) { @@ -78,15 +84,25 @@ func TestRouting_addBotHandler(t *testing.T) { if err != nil { t.Fatal(err) } - rr := httptest.NewRecorder() handler := http.HandlerFunc(addBotHandler) handler.ServeHTTP(rr, req) + require.Equal(t, http.StatusCreated, rr.Code, + fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusCreated)) - if rr.Code != http.StatusCreated { - t.Errorf("handler returned wrong status code: got %v want %v", - rr.Code, http.StatusCreated) + bytes, err := ioutil.ReadAll(rr.Body) + if err != nil { + t.Fatal(err) } + + var res map[string]interface{} + + err = json.Unmarshal(bytes, &res) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, "123123:Qwerty", res["token"]) } func TestRouting_activityBotHandler(t *testing.T) { @@ -109,10 +125,8 @@ func TestRouting_activityBotHandler(t *testing.T) { handler := http.HandlerFunc(activityBotHandler) handler.ServeHTTP(rr, req) - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - rr.Code, http.StatusOK) - } + assert.Equal(t, http.StatusOK, rr.Code, + fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)) } func TestRouting_settingsHandler(t *testing.T) { @@ -125,10 +139,8 @@ func TestRouting_settingsHandler(t *testing.T) { handler := http.HandlerFunc(makeHandler(settingsHandler)) handler.ServeHTTP(rr, req) - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - rr.Code, http.StatusOK) - } + assert.Equal(t, http.StatusOK, rr.Code, + fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)) } func TestRouting_saveHandler(t *testing.T) { @@ -153,80 +165,10 @@ func TestRouting_saveHandler(t *testing.T) { handler := http.HandlerFunc(saveHandler) handler.ServeHTTP(rr, req) - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - rr.Code, http.StatusOK) - } + assert.Equal(t, http.StatusOK, rr.Code, + fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)) } -// -//func TestRouting_createHandler(t *testing.T) { -// defer gock.Off() -// -// gock.New("https://test2.retailcrm.ru"). -// Get("/api/credentials"). -// Reply(200). -// BodyString(`{"success": true}`) -// -// integrationModule := v5.IntegrationModule{ -// Code: transport, -// IntegrationCode: transport, -// Active: true, -// Name: "Telegram", -// ClientID: "123", -// Logo: fmt.Sprintf( -// "https://%s/web/telegram_logo.svg", -// config.HTTPServer.Host, -// ), -// BaseURL: fmt.Sprintf( -// "https://%s", -// config.HTTPServer.Host, -// ), -// AccountURL: fmt.Sprintf( -// "https://%s/settings/%s", -// config.HTTPServer.Host, -// "123", -// ), -// Actions: map[string]string{"activity": "/actions/activity"}, -// Integrations: &v5.Integrations{ -// MgTransport: &v5.MgTransport{ -// WebhookUrl: fmt.Sprintf( -// "https://%s/webhook", -// config.HTTPServer.Host, -// ), -// }, -// }, -// } -// -// updateJSON, _ := json.Marshal(&integrationModule) -// p := url.Values{"integrationModule": {string(updateJSON[:])}} -// -// gock.New("https://test2.retailcrm.ru"). -// Post(fmt.Sprintf("/api/v5/integration-modules/%s/edit", integrationModule.Code)). -// MatchType("url"). -// BodyString(p.Encode()). -// MatchHeader("X-API-KEY", "test"). -// Reply(201). -// BodyString(`{"success": true, "info": {"baseUrl": "http://test.te", "token": "test"}}`) -// -// req, err := http.NewRequest("POST", "/create/", -// strings.NewReader( -// `{"api_url": "https://test2.retailcrm.ru","api_key": "test"}`, -// )) -// if err != nil { -// t.Fatal(err) -// } -// -// rr := httptest.NewRecorder() -// handler := http.HandlerFunc(createHandler) -// handler.ServeHTTP(rr, req) -// -// if rr.Code != http.StatusFound { -// t.Errorf("handler returned wrong status code: got %v want %v", -// rr.Code, http.StatusFound) -// } -//} - func TestRouting_activityHandler(t *testing.T) { req, err := http.NewRequest("POST", "/actions/activity", strings.NewReader( @@ -240,8 +182,41 @@ func TestRouting_activityHandler(t *testing.T) { handler := http.HandlerFunc(activityHandler) handler.ServeHTTP(rr, req) - if rr.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - rr.Code, http.StatusOK) - } + assert.Equal(t, http.StatusOK, rr.Code, + fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)) +} + +func TestRouting_TranslateLoader(t *testing.T) { + type m map[string]string + te := [][]string{} + + dt := "translate" + + files, err := ioutil.ReadDir(dt) + if err != nil { + t.Fatal(err) + } + + for _, f := range files { + ms := m{} + if !f.IsDir() { + res, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", dt, f.Name())) + if err != nil { + t.Fatal(err) + } + + err = yaml.Unmarshal(res, &ms) + if err != nil { + t.Fatal(err) + } + + keys := []string{} + for kms := range ms { + keys = append(keys, kms) + } + sort.Strings(keys) + te = append(te, keys) + } + } + } diff --git a/telegram.go b/telegram.go index 0a9939d..11916a7 100644 --- a/telegram.go +++ b/telegram.go @@ -1,10 +1,9 @@ package main import ( - "net/http" - "encoding/json" "io/ioutil" + "net/http" "strconv" "time" @@ -33,26 +32,26 @@ func GetBotName(bot *tgbotapi.BotAPI) string { } func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string) { - b := getBotByToken(token) - if b.ID == 0 { + c, err := getConnectionByBotToken(token) + if err != nil { + raven.CaptureErrorAndWait(err, nil) + logger.Error(token, err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if len(c.Bots) == 0 { logger.Error(token, "missing") w.WriteHeader(http.StatusBadRequest) return } - if !b.Active { + if !c.Bots[0].Active { logger.Error(token, "deactivated") w.WriteHeader(http.StatusBadRequest) return } - c := getConnectionById(b.ConnectionID) - if c.MGURL == "" || c.MGToken == "" { - logger.Error(token, "MGURL or MGToken is empty") - w.WriteHeader(http.StatusBadRequest) - return - } - var update tgbotapi.Update bytes, err := ioutil.ReadAll(r.Body) @@ -90,11 +89,12 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string Lastname: update.Message.From.LastName, Language: update.Message.From.LanguageCode, }, - Channel: b.Channel, + Channel: c.Bots[0].Channel, } data, st, err := client.Messages(snd) if err != nil { + raven.CaptureErrorAndWait(err, nil) logger.Error(token, err.Error(), st, data) w.WriteHeader(http.StatusInternalServerError) return @@ -110,11 +110,12 @@ func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string Text: update.EditedMessage.Text, }, }, - Channel: b.Channel, + Channel: c.Bots[0].Channel, } data, st, err := client.UpdateMessages(snd) if err != nil { + raven.CaptureErrorAndWait(err, nil) logger.Error(token, err.Error(), st, data) w.WriteHeader(http.StatusInternalServerError) return @@ -163,6 +164,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) { if msg.Type == "message_sent" { msg, err := bot.Send(tgbotapi.NewMessage(msg.Data.ExternalChatID, msg.Data.Content)) if err != nil { + raven.CaptureErrorAndWait(err, nil) logger.Error(err) w.WriteHeader(http.StatusBadRequest) return @@ -176,6 +178,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) { if msg.Type == "message_updated" { msg, err := bot.Send(tgbotapi.NewEditMessageText(msg.Data.ExternalChatID, uid, msg.Data.Content)) if err != nil { + raven.CaptureErrorAndWait(err, nil) logger.Error(err) w.WriteHeader(http.StatusBadRequest) return @@ -189,6 +192,7 @@ func mgWebhookHandler(w http.ResponseWriter, r *http.Request) { if msg.Type == "message_deleted" { msg, err := bot.Send(tgbotapi.NewDeleteMessage(msg.Data.ExternalChatID, uid)) if err != nil { + raven.CaptureErrorAndWait(err, nil) logger.Error(err) w.WriteHeader(http.StatusBadRequest) return