Initial release (#1)
Routes Migrations Settings form Activation/deactivation through retailCRM/MG Transport API
This commit is contained in:
parent
fce425c90a
commit
acb2a62da5
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
gin-bin
|
||||
config.yml
|
||||
.idea/
|
||||
/bin/
|
||||
mg-telegram
|
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM golang:1.9.3-stretch as ca-certs
|
||||
FROM scratch
|
||||
|
||||
COPY --from=ca-certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
ENTRYPOINT ["/mg-telegram", "--config", "/config.yml"]
|
||||
|
||||
CMD ["run"]
|
35
Makefile
Normal file
35
Makefile
Normal file
@ -0,0 +1,35 @@
|
||||
ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
SRC_DIR=$(ROOT_DIR)
|
||||
CONFIG_FILE=$(ROOT_DIR)/config.yml
|
||||
CONFIG_TEST_FILE=$(ROOT_DIR)/config_test.yml
|
||||
BIN=$(ROOT_DIR)/mg-telegram
|
||||
REVISION=$(shell git describe --tags 2>/dev/null || git log --format="v0.0-%h" -n 1 || echo "v0.0-unknown")
|
||||
|
||||
ifndef GOPATH
|
||||
$(error GOPATH must be defined)
|
||||
endif
|
||||
|
||||
export GOPATH := $(GOPATH):$(ROOT_DIR)
|
||||
|
||||
fmt:
|
||||
@echo "==> Running gofmt"
|
||||
@gofmt -l -s -w $(SRC_DIR)
|
||||
|
||||
install: fmt
|
||||
@echo "==> Running go get"
|
||||
$(eval DEPS:=$(shell cd $(SRC_DIR) \
|
||||
&& go list -f '{{join .Imports "\n"}}{{ "\n" }}{{join .TestImports "\n"}}' ./... \
|
||||
| sort | uniq | tr '\r' '\n' | paste -sd ' ' -))
|
||||
@go get -d -v $(DEPS)
|
||||
|
||||
build: install
|
||||
@echo "==> Building"
|
||||
@go build -o $(BIN) -ldflags "-X common.build=${REVISION}" .
|
||||
@echo $(BIN)
|
||||
|
||||
migrate: build
|
||||
@${BIN} --config $(CONFIG_FILE) migrate -p ./migrations/
|
||||
|
||||
run: migrate
|
||||
@echo "==> Running"
|
||||
@${BIN} --config $(CONFIG_FILE) run
|
55
config.go
Normal file
55
config.go
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
logging "github.com/op/go-logging"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// TransportConfig struct
|
||||
type TransportConfig struct {
|
||||
LogLevel logging.Level `yaml:"log_level"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
SentryDSN string `yaml:"sentry_dsn"`
|
||||
HTTPServer HTTPServerConfig `yaml:"http_server"`
|
||||
}
|
||||
|
||||
// DatabaseConfig struct
|
||||
type DatabaseConfig struct {
|
||||
Connection string `yaml:"connection"`
|
||||
Logging bool `yaml:"logging"`
|
||||
TablePrefix string `yaml:"table_prefix"`
|
||||
MaxOpenConnections int `yaml:"max_open_connections"`
|
||||
MaxIdleConnections int `yaml:"max_idle_connections"`
|
||||
ConnectionLifetime int `yaml:"connection_lifetime"`
|
||||
}
|
||||
|
||||
// HTTPServerConfig struct
|
||||
type HTTPServerConfig struct {
|
||||
Host string `yaml:"host"`
|
||||
Listen string `yaml:"listen"`
|
||||
}
|
||||
|
||||
// LoadConfig read configuration file
|
||||
func LoadConfig(path string) *TransportConfig {
|
||||
var err error
|
||||
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
source, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var config TransportConfig
|
||||
if err = yaml.Unmarshal(source, &config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &config
|
||||
}
|
10
config.yml.dist
Normal file
10
config.yml.dist
Normal file
@ -0,0 +1,10 @@
|
||||
database:
|
||||
connection: postgres://mg_telegram:mg_telegram@postgres:5432/mg_telegram?sslmode=disable
|
||||
|
||||
http_server:
|
||||
host: ~
|
||||
listen: :3001
|
||||
|
||||
sentry_dsn: ~
|
||||
|
||||
log_level: 5
|
10
config_test.yml.dist
Normal file
10
config_test.yml.dist
Normal file
@ -0,0 +1,10 @@
|
||||
database:
|
||||
connection: postgres://mg_telegram_test:mg_telegram_test@postgres_test:5450/mg_telegram_test?sslmode=disable
|
||||
|
||||
http_server:
|
||||
host: ~
|
||||
listen: :3001
|
||||
|
||||
sentry_dsn: ~
|
||||
|
||||
log_level: 5
|
51
database.go
Normal file
51
database.go
Normal file
@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
)
|
||||
|
||||
// Orm struct
|
||||
type Orm struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// NewDb init new database connection
|
||||
func NewDb(config *TransportConfig) *Orm {
|
||||
db, err := gorm.Open("postgres", config.Database.Connection)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db.DB().SetConnMaxLifetime(time.Duration(config.Database.ConnectionLifetime) * time.Second)
|
||||
db.DB().SetMaxOpenConns(config.Database.MaxOpenConnections)
|
||||
db.DB().SetMaxIdleConns(config.Database.MaxIdleConnections)
|
||||
|
||||
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
|
||||
return config.Database.TablePrefix + defaultTableName
|
||||
}
|
||||
|
||||
db.SingularTable(true)
|
||||
db.LogMode(config.Database.Logging)
|
||||
|
||||
setCreatedAt := func(scope *gorm.Scope) {
|
||||
if scope.HasColumn("CreatedAt") {
|
||||
scope.SetColumn("CreatedAt", time.Now())
|
||||
}
|
||||
}
|
||||
|
||||
db.Callback().Create().Replace("gorm:update_time_stamp", setCreatedAt)
|
||||
|
||||
return &Orm{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Close connection
|
||||
func (orm *Orm) Close() {
|
||||
orm.DB.Close()
|
||||
}
|
21
docker-compose-test.yml
Normal file
21
docker-compose-test.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: '2.1'
|
||||
|
||||
services:
|
||||
postgres_test:
|
||||
image: postgres:9.6
|
||||
environment:
|
||||
POSTGRES_USER: mg_telegram_test
|
||||
POSTGRES_PASSWORD: mg_telegram_test
|
||||
POSTGRES_DATABASE: mg_telegram_test
|
||||
ports:
|
||||
- ${POSTGRES_ADDRESS:-127.0.0.1:5450}:${POSTGRES_PORT:-5450}
|
||||
command: -p ${POSTGRES_PORT:-5450}
|
||||
|
||||
mg_telegram_test:
|
||||
image: golang:1.9.3-stretch
|
||||
working_dir: /mg_telegram
|
||||
user: ${UID:-1000}:${GID:-1000}
|
||||
volumes:
|
||||
- ./:/mg_telegram/
|
||||
links:
|
||||
- postgres_test
|
23
docker-compose.yml
Normal file
23
docker-compose.yml
Normal file
@ -0,0 +1,23 @@
|
||||
version: '2.1'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:9.6
|
||||
environment:
|
||||
POSTGRES_USER: mg_telegram
|
||||
POSTGRES_PASSWORD: mg_telegram
|
||||
POSTGRES_DATABASE: mg_telegram
|
||||
ports:
|
||||
- ${POSTGRES_ADDRESS:-127.0.0.1:5434}:5432
|
||||
|
||||
mg_telegram:
|
||||
image: golang:1.9.3-stretch
|
||||
working_dir: /mg_telegram
|
||||
user: ${UID:-1000}:${GID:-1000}
|
||||
volumes:
|
||||
- ./:/mg_telegram/
|
||||
links:
|
||||
- postgres
|
||||
ports:
|
||||
- ${MG_TELEGRAM_ADDRESS:-3001}:3001
|
||||
command: make run
|
22
log.go
Normal file
22
log.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
var logFormat = logging.MustStringFormatter(
|
||||
`%{time:2006-01-02 15:04:05.000} %{level:.4s} => %{message}`,
|
||||
)
|
||||
|
||||
func newLogger() *logging.Logger {
|
||||
logger := logging.MustGetLogger(transport)
|
||||
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
|
||||
formatBackend := logging.NewBackendFormatter(logBackend, logFormat)
|
||||
backend1Leveled := logging.AddModuleLevel(logBackend)
|
||||
backend1Leveled.SetLevel(config.LogLevel, "")
|
||||
logging.SetBackend(formatBackend)
|
||||
|
||||
return logger
|
||||
}
|
32
main.go
Normal file
32
main.go
Normal file
@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
// Options struct
|
||||
type Options struct {
|
||||
Config string `short:"c" long:"config" default:"config.yml" description:"Path to configuration file"`
|
||||
}
|
||||
|
||||
const transport = "mg-telegram"
|
||||
|
||||
var options Options
|
||||
var parser = flags.NewParser(&options, flags.Default)
|
||||
|
||||
func init() {
|
||||
raven.SetDSN(config.SentryDSN)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
83
migrate.go
Normal file
83
migrate.go
Normal file
@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang-migrate/migrate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
parser.AddCommand("migrate",
|
||||
"Migrate database to defined migrations version",
|
||||
"Migrate database to defined migrations version.",
|
||||
&MigrateCommand{},
|
||||
)
|
||||
}
|
||||
|
||||
// MigrateCommand struct
|
||||
type MigrateCommand struct {
|
||||
Version string `short:"v" long:"version" default:"up" description:"Migrate to defined migrations version. Allowed: up, down, next, prev and integer value."`
|
||||
Path string `short:"p" long:"path" default:"" description:"Path to migrations files."`
|
||||
}
|
||||
|
||||
// Execute method
|
||||
func (x *MigrateCommand) Execute(args []string) error {
|
||||
config := LoadConfig(options.Config)
|
||||
|
||||
err := Migrate(config.Database.Connection, x.Version, x.Path)
|
||||
if err != nil && err.Error() == "no change" {
|
||||
fmt.Println("No changes detected. Skipping migration.")
|
||||
err = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Migrate function
|
||||
func Migrate(database string, version string, path string) error {
|
||||
m, err := migrate.New("file://"+path, database)
|
||||
if err != nil {
|
||||
fmt.Printf("Migrations path %s does not exist or permission denied\n", path)
|
||||
return err
|
||||
}
|
||||
|
||||
defer m.Close()
|
||||
|
||||
currentVersion, _, err := m.Version()
|
||||
if "up" == version {
|
||||
fmt.Printf("Migrating from %d to last\n", currentVersion)
|
||||
return m.Up()
|
||||
}
|
||||
|
||||
if "down" == version {
|
||||
fmt.Printf("Migrating from %d to 0\n", currentVersion)
|
||||
return m.Down()
|
||||
}
|
||||
|
||||
if "next" == version {
|
||||
fmt.Printf("Migrating from %d to next\n", currentVersion)
|
||||
return m.Steps(1)
|
||||
}
|
||||
|
||||
if "prev" == version {
|
||||
fmt.Printf("Migrating from %d to previous\n", currentVersion)
|
||||
return m.Steps(-1)
|
||||
}
|
||||
|
||||
ver, err := strconv.ParseUint(version, 10, 32)
|
||||
if err != nil {
|
||||
fmt.Printf("Invalid migration version %s\n", version)
|
||||
return err
|
||||
}
|
||||
|
||||
if ver != 0 {
|
||||
fmt.Printf("Migrating from %d to %d\n", currentVersion, ver)
|
||||
return m.Migrate(uint(ver))
|
||||
}
|
||||
|
||||
fmt.Printf("Migrations not found in path %s\n", path)
|
||||
|
||||
return errors.New("migrations not found")
|
||||
}
|
1
migrations/1525942800_app.down.sql
Normal file
1
migrations/1525942800_app.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE connection;
|
15
migrations/1525942800_app.up.sql
Normal file
15
migrations/1525942800_app.up.sql
Normal file
@ -0,0 +1,15 @@
|
||||
create table connection
|
||||
(
|
||||
id serial not null
|
||||
constraint connection_pkey
|
||||
primary key,
|
||||
client_id text,
|
||||
api_key text,
|
||||
api_url text,
|
||||
mg_url text,
|
||||
mg_token text,
|
||||
created_at timestamp with time zone,
|
||||
updated_at timestamp with time zone,
|
||||
active boolean
|
||||
);
|
||||
|
1
migrations/1525944630_app.down.sql
Normal file
1
migrations/1525944630_app.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE bot;
|
14
migrations/1525944630_app.up.sql
Normal file
14
migrations/1525944630_app.up.sql
Normal file
@ -0,0 +1,14 @@
|
||||
create table bot
|
||||
(
|
||||
id serial not null
|
||||
constraint bot_pkey
|
||||
primary key,
|
||||
client_id text,
|
||||
channel bigint,
|
||||
token text,
|
||||
name text,
|
||||
created_at timestamp with time zone,
|
||||
updated_at timestamp with time zone,
|
||||
active boolean
|
||||
);
|
||||
|
1
migrations/1526308450_app.down.sql
Normal file
1
migrations/1526308450_app.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE mapping;
|
9
migrations/1526308450_app.up.sql
Normal file
9
migrations/1526308450_app.up.sql
Normal file
@ -0,0 +1,9 @@
|
||||
create table mapping
|
||||
(
|
||||
id serial not null
|
||||
constraint mapping_pkey
|
||||
primary key,
|
||||
site_code text,
|
||||
bot_id text
|
||||
);
|
||||
|
38
models.go
Normal file
38
models.go
Normal file
@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
// Connection model
|
||||
type Connection struct {
|
||||
ID int `gorm:"primary_key"`
|
||||
ClientID string `gorm:"client_id" json:"clientId,omitempty"`
|
||||
APIKEY string `gorm:"api_key" json:"api_key,omitempty"`
|
||||
APIURL string `gorm:"api_url" json:"api_url,omitempty"`
|
||||
MGURL string `gorm:"mg_url" json:"mg_url,omitempty"`
|
||||
MGToken string `gorm:"mg_token" json:"mg_token,omitempty"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Active bool `json:"active,omitempty"`
|
||||
}
|
||||
|
||||
// Bot model
|
||||
type Bot struct {
|
||||
ID int `gorm:"primary_key"`
|
||||
ClientID string `gorm:"client_id" json:"clientId,omitempty"`
|
||||
Channel uint64 `json:"channel,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Active bool `json:"active,omitempty"`
|
||||
}
|
||||
|
||||
// Mapping model
|
||||
type Mapping struct {
|
||||
ID int `gorm:"primary_key"`
|
||||
SiteCode string `gorm:"site_code" json:"site_code,omitempty"`
|
||||
BotID string `gorm:"bot_id" json:"bot_id,omitempty"`
|
||||
}
|
||||
|
||||
//Bots list
|
||||
type Bots []Bot
|
76
repository.go
Normal file
76
repository.go
Normal file
@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
func createMapping(s []Mapping) error {
|
||||
tx := orm.DB.Begin()
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Warning(r)
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, val := range s {
|
||||
if err := tx.Create(&val).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
func getConnection(uid string) (*Connection, error) {
|
||||
var connection Connection
|
||||
orm.DB.First(&connection, "client_id = ?", uid)
|
||||
|
||||
return &connection, nil
|
||||
}
|
||||
|
||||
func getConnectionByURL(urlCrm string) (*Connection, error) {
|
||||
var connection Connection
|
||||
orm.DB.First(&connection, "api_url = ?", urlCrm)
|
||||
|
||||
return &connection, nil
|
||||
}
|
||||
|
||||
func (c *Connection) setConnectionActivity() error {
|
||||
return orm.DB.Model(c).Where("client_id = ?", c.ClientID).Update("Active", c.Active).Error
|
||||
}
|
||||
|
||||
func (c *Connection) createConnection() error {
|
||||
return orm.DB.Create(c).Error
|
||||
}
|
||||
|
||||
func (c *Connection) saveConnection() error {
|
||||
return orm.DB.Model(c).Where("client_id = ?", c.ClientID).Update(c).Error
|
||||
}
|
||||
|
||||
func getBotByToken(token string) (*Bot, error) {
|
||||
var bot Bot
|
||||
orm.DB.First(&bot, "token = ?", token)
|
||||
|
||||
return &bot, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func getBotChannelByToken(token string) uint64 {
|
||||
var b Bot
|
||||
orm.DB.First(&b, "token = ?", token)
|
||||
|
||||
return b.Channel
|
||||
}
|
||||
|
||||
func (b *Bots) getBotsByClientID(uid string) error {
|
||||
return orm.DB.Where("client_id = ?", uid).Find(b).Error
|
||||
}
|
433
routing.go
Normal file
433
routing.go
Normal file
@ -0,0 +1,433 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/retailcrm/api-client-go/v5"
|
||||
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
templates = template.Must(template.ParseFiles("templates/form.html", "templates/home.html"))
|
||||
validPath = regexp.MustCompile("^/(save|settings)/([a-zA-Z0-9]+)$")
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func renderTemplate(w http.ResponseWriter, tmpl string, c interface{}) {
|
||||
err := templates.ExecuteTemplate(w, tmpl+".html", 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
|
||||
}
|
||||
fn(w, r, m[2])
|
||||
}
|
||||
}
|
||||
|
||||
func connectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
p := Connection{}
|
||||
renderTemplate(w, "home", &p)
|
||||
}
|
||||
|
||||
func addBotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var b Bot
|
||||
|
||||
err = json.Unmarshal(body, &b)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if b.Token == "" {
|
||||
http.Error(w, "set bot token", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
cl, _ := getBotByToken(b.Token)
|
||||
if cl.ID != 0 {
|
||||
http.Error(w, "bot already created", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
bot, err := GetBotInfo(b.Token)
|
||||
if err != nil {
|
||||
logger.Error(b.Token, err.Error())
|
||||
http.Error(w, "set correct bot token", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
b.Name = GetBotName(bot)
|
||||
|
||||
c, err := getConnection(b.ClientID)
|
||||
if err != nil {
|
||||
http.Error(w, "could not find account, please contact technical support", http.StatusInternalServerError)
|
||||
logger.Error(b.ClientID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if c.MGURL == "" || c.MGToken == "" {
|
||||
http.Error(w, "could not find account, please contact technical support", http.StatusInternalServerError)
|
||||
logger.Error(b.ClientID)
|
||||
return
|
||||
}
|
||||
|
||||
ch := v1.Channel{
|
||||
Type: "telegram",
|
||||
Events: []string{
|
||||
"message_sent",
|
||||
"message_updated",
|
||||
"message_deleted",
|
||||
"message_read",
|
||||
},
|
||||
}
|
||||
|
||||
var client = v1.New(c.MGURL, c.MGToken)
|
||||
data, status, err := client.ActivateTransportChannel(ch)
|
||||
if status != http.StatusCreated {
|
||||
http.Error(w, "error while activating the channel", http.StatusInternalServerError)
|
||||
logger.Error(c.APIURL, status, err.Error(), data)
|
||||
return
|
||||
}
|
||||
|
||||
b.Channel = data.ChannelID
|
||||
b.Active = true
|
||||
|
||||
err = b.createBot()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func activityBotHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var b Bot
|
||||
|
||||
err = json.Unmarshal(body, &b)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ch := v1.Channel{
|
||||
ID: getBotChannelByToken(b.Token),
|
||||
Type: "telegram",
|
||||
Events: []string{
|
||||
"message_sent",
|
||||
"message_updated",
|
||||
"message_deleted",
|
||||
"message_read",
|
||||
},
|
||||
}
|
||||
|
||||
c, err := getConnection(b.ClientID)
|
||||
if err != nil {
|
||||
http.Error(w, "could not find account, please contact technical support", http.StatusInternalServerError)
|
||||
logger.Error(b.ClientID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if c.MGURL == "" || c.MGToken == "" {
|
||||
http.Error(w, "could not find account, please contact technical support", http.StatusInternalServerError)
|
||||
logger.Error(b.ClientID, "could not find account, please contact technical support")
|
||||
return
|
||||
}
|
||||
|
||||
var client = v1.New(c.MGURL, c.MGToken)
|
||||
|
||||
if b.Active {
|
||||
data, status, err := client.DeactivateTransportChannel(ch.ID)
|
||||
if status > http.StatusOK {
|
||||
http.Error(w, "error while deactivating the channel", http.StatusInternalServerError)
|
||||
logger.Error(b.ClientID, status, err.Error(), data)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
data, status, err := client.ActivateTransportChannel(ch)
|
||||
if status > http.StatusCreated {
|
||||
http.Error(w, "error while activating the channel", http.StatusInternalServerError)
|
||||
logger.Error(b.ClientID, status, err.Error(), data)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = b.setBotActivity()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func mappingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var rec []Mapping
|
||||
|
||||
err = json.Unmarshal(body, &rec)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = createMapping(rec)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) {
|
||||
p, err := getConnection(uid)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if p.ID == 0 {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
bots := Bots{}
|
||||
bots.getBotsByClientID(uid)
|
||||
|
||||
client := v5.New(p.APIURL, p.APIKEY)
|
||||
sites, _, _ := client.Sites()
|
||||
|
||||
res := struct {
|
||||
Conn *Connection
|
||||
Bots Bots
|
||||
Sites map[string]v5.Site
|
||||
}{
|
||||
p,
|
||||
bots,
|
||||
sites.Sites,
|
||||
}
|
||||
|
||||
renderTemplate(w, "form", res)
|
||||
}
|
||||
|
||||
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var c Connection
|
||||
|
||||
err = json.Unmarshal(body, &c)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = validate(c)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = c.saveConnection()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("/settings/" + r.FormValue("clientId")))
|
||||
}
|
||||
|
||||
func createHandler(w http.ResponseWriter, r *http.Request) {
|
||||
c := Connection{
|
||||
ClientID: GenerateToken(),
|
||||
APIURL: string([]byte(r.FormValue("api_url"))),
|
||||
APIKEY: string([]byte(r.FormValue("api_key"))),
|
||||
}
|
||||
|
||||
err := validate(c)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cl, _ := getConnectionByURL(c.APIURL)
|
||||
if cl.ID != 0 {
|
||||
http.Error(w, "connection already created", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
client := v5.New(c.APIURL, c.APIKEY)
|
||||
|
||||
cr, status, errr := client.APICredentials()
|
||||
if errr.RuntimeErr != nil {
|
||||
http.Error(w, "set correct crm url or key", http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, status, err.Error(), cr)
|
||||
return
|
||||
}
|
||||
|
||||
if !cr.Success {
|
||||
http.Error(w, "set correct crm url or key", http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, status, err.Error(), cr)
|
||||
return
|
||||
}
|
||||
|
||||
integration := v5.IntegrationModule{
|
||||
Code: transport,
|
||||
IntegrationCode: transport,
|
||||
Active: true,
|
||||
Name: "MG Telegram",
|
||||
ClientID: c.ClientID,
|
||||
BaseURL: config.HTTPServer.Host,
|
||||
AccountURL: fmt.Sprintf(
|
||||
"%s/settings/%s",
|
||||
config.HTTPServer.Host,
|
||||
c.ClientID,
|
||||
),
|
||||
Actions: map[string]string{"activity": "/actions/activity"},
|
||||
Integrations: &v5.Integrations{
|
||||
MgTransport: &v5.MgTransport{
|
||||
WebhookUrl: fmt.Sprintf(
|
||||
"%s/webhook",
|
||||
config.HTTPServer.Host,
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, status, errr := client.IntegrationModuleEdit(integration)
|
||||
if errr.RuntimeErr != nil {
|
||||
http.Error(w, "error while creating integration", http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, status, errr.RuntimeErr, data)
|
||||
return
|
||||
}
|
||||
|
||||
if status >= http.StatusBadRequest {
|
||||
http.Error(w, errr.ApiErr, http.StatusBadRequest)
|
||||
logger.Error(c.APIURL, status, errr.ApiErr, data)
|
||||
return
|
||||
}
|
||||
|
||||
c.MGURL = data.Info["baseUrl"]
|
||||
c.MGToken = data.Info["token"]
|
||||
|
||||
err = c.createConnection()
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
http.Error(w, "error while creating connection", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusFound)
|
||||
w.Write([]byte("/settings/" + c.ClientID))
|
||||
}
|
||||
|
||||
func activityHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
res := Response{Success: false}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
res.Error = "set POST"
|
||||
jsonString, _ := json.Marshal(res)
|
||||
w.Write(jsonString)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
res.Error = "incorrect data"
|
||||
jsonString, _ := json.Marshal(res)
|
||||
w.Write(jsonString)
|
||||
return
|
||||
}
|
||||
|
||||
var rec Connection
|
||||
|
||||
err = json.Unmarshal(body, &rec)
|
||||
if err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
res.Error = "incorrect data"
|
||||
jsonString, _ := json.Marshal(res)
|
||||
w.Write(jsonString)
|
||||
return
|
||||
}
|
||||
|
||||
if err := rec.setConnectionActivity(); err != nil {
|
||||
raven.CaptureErrorAndWait(err, nil)
|
||||
res.Error = "incorrect data"
|
||||
jsonString, _ := json.Marshal(res)
|
||||
w.Write(jsonString)
|
||||
return
|
||||
}
|
||||
|
||||
res.Success = true
|
||||
jsonString, _ := json.Marshal(res)
|
||||
w.Write(jsonString)
|
||||
}
|
||||
|
||||
func validate(c Connection) error {
|
||||
if c.APIURL == "" || c.APIKEY == "" {
|
||||
return errors.New("missing crm url or key")
|
||||
}
|
||||
|
||||
if res, _ := regexp.MatchString(`https://?[\da-z\.-]+\.(retailcrm\.(ru|pro)|ecomlogic\.com)`, c.APIURL); !res {
|
||||
return errors.New("set correct crm url")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
53
run.go
Normal file
53
run.go
Normal file
@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
_ "github.com/golang-migrate/migrate/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/source/file"
|
||||
)
|
||||
|
||||
var (
|
||||
config = LoadConfig("config.yml")
|
||||
orm = NewDb(config)
|
||||
logger = newLogger()
|
||||
)
|
||||
|
||||
func init() {
|
||||
parser.AddCommand("run",
|
||||
"Run mg-telegram",
|
||||
"Run mg-telegram.",
|
||||
&RunCommand{},
|
||||
)
|
||||
}
|
||||
|
||||
// RunCommand struct
|
||||
type RunCommand struct{}
|
||||
|
||||
// Execute command
|
||||
func (x *RunCommand) Execute(args []string) error {
|
||||
go start()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c)
|
||||
for sig := range c {
|
||||
switch sig {
|
||||
case os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM:
|
||||
orm.DB.Close()
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func start() {
|
||||
setWrapperRoutes()
|
||||
setTransportRoutes()
|
||||
http.ListenAndServe(config.HTTPServer.Listen, nil)
|
||||
}
|
27
telegram.go
Normal file
27
telegram.go
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
func setTransportRoutes() {
|
||||
http.HandleFunc("/add-bot/", addBotHandler)
|
||||
http.HandleFunc("/activity-bot/", activityBotHandler)
|
||||
http.HandleFunc("/map-bot/", mappingHandler)
|
||||
}
|
||||
|
||||
// GetBotInfo function
|
||||
func GetBotInfo(token string) (*tgbotapi.BotAPI, error) {
|
||||
bot, err := tgbotapi.NewBotAPI(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bot, nil
|
||||
}
|
||||
|
||||
// GetBotName function
|
||||
func GetBotName(bot *tgbotapi.BotAPI) string {
|
||||
return bot.Self.FirstName
|
||||
}
|
199
templates/form.html
Normal file
199
templates/form.html
Normal file
@ -0,0 +1,199 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta title="Telegram transport">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-alpha.4/css/materialize.min.css">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-alpha.4/js/materialize.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row" style="margin-top: 5%">
|
||||
<div class="col s8 offset-s3">
|
||||
<ul class="tabs" id="tab">
|
||||
<li class="tab col s3"><a class="active" href="#tab1">Settings CRM</a></li>
|
||||
<li class="tab col s3"><a href="#tab2">Bots</a></li>
|
||||
<li class="tab col s3"><a href="#tab3">Mapping</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col s8 offset-s3">
|
||||
<p id="msg"></p>
|
||||
</div>
|
||||
<div id="tab1" class="col s12">
|
||||
<div class="row" style="margin-top: 5%">
|
||||
<form class="col s8 offset-s2" action="/save/" method="POST">
|
||||
<input name="clientId" type="hidden" value="{{.Conn.ClientID}}">
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<input placeholder="API Url" id="api_url" name="api_url" type="text" class="validate" value="{{.Conn.APIURL}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<input placeholder="API Key" id="api_key" name="api_key" type="text" class="validate" value="{{.Conn.APIKEY}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 offset-s5">
|
||||
<button class="btn waves-effect waves-light red lighten-1" type="submit" name="action">
|
||||
Connect
|
||||
<i class="material-icons right">sync</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab2" class="col s12">
|
||||
<div class="row" style="margin-top: 5%">
|
||||
<form class="col s8 offset-s2" action="/add-bot/" method="POST">
|
||||
<input name="clientId" type="hidden" value="{{.Conn.ClientID}}">
|
||||
<div class="row">
|
||||
<div class="input-field col s11">
|
||||
<input placeholder="Bot Token" id="token" name="token" type="text" class="validate">
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<button class="btn btn-meddium waves-effect waves-light red" type="submit" name="action">
|
||||
<i class="material-icons">add</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{if .Bots}}
|
||||
<table class="col s8 offset-s2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th><th>Token</th><th style="text-align: right">Activity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Bots}}
|
||||
<tr>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{.Token}}</td>
|
||||
<td>
|
||||
<button class="activity-bot btn btn-meddium waves-effect waves-light red" type="submit" name="action" style="float: right"
|
||||
data-activity="{{.Active}}" data-id="{{.Token}}">
|
||||
<i class="material-icons">{{if .Active}}stop{{else}}play_arrow{{end}}</i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab3" class="col s12">
|
||||
<div class="row" style="margin-top: 5%">
|
||||
{{ $sites := .Sites }}
|
||||
{{ $bots := .Bots }}
|
||||
{{if and $sites $bots}}
|
||||
<table class="col s8 offset-s2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sites</th>
|
||||
<th>Bots</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $site := $sites}}
|
||||
<tr>
|
||||
<td>{{$site.Name}}</td>
|
||||
<td>
|
||||
<select class="browser-default">
|
||||
<option value="" disabled selected>Choose your option</option>
|
||||
{{range $bot := $bots}}
|
||||
<option data-code="{{$site.Code}}" data-bot="{{$bot.ID}}">{{$bot.Name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 offset-s5">
|
||||
<button class="btn btn-meddium waves-effect waves-light red" type="submit" id="map-bot">
|
||||
save
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("form").each(function() {
|
||||
$(this).on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
send($(this).attr('action'), formDataToObj($(this).serializeArray()))
|
||||
});
|
||||
});
|
||||
|
||||
$('.activity-bot').on("click", function(e) {
|
||||
send("/activity-bot/", {
|
||||
token: $(this).attr("data-id"),
|
||||
active: ($(this).attr("data-activity") == 'true'),
|
||||
clientId: $('input[name=clientId]').val(),
|
||||
})
|
||||
});
|
||||
|
||||
$('#map-bot').on("click", function(e) {
|
||||
let mapping = [];
|
||||
|
||||
$('select option:selected').each(function() {
|
||||
let site_code = $(this).attr("data-code");
|
||||
let bot_id = $(this).attr("data-bot");
|
||||
if (site_code && bot_id) {
|
||||
mapping.push({site_code: site_code, bot_id: bot_id});
|
||||
}
|
||||
});
|
||||
|
||||
if (!$.isEmptyObject(mapping)) {
|
||||
send("/map-bot/", mapping);
|
||||
}
|
||||
});
|
||||
|
||||
function send(url, data) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
data: JSON.stringify(data),
|
||||
type: "POST",
|
||||
success: function(res){
|
||||
location.reload();
|
||||
},
|
||||
error: function (res){
|
||||
if (res.status < 400) {
|
||||
if (res.responseText) {
|
||||
document.location.replace(
|
||||
location.protocol.concat("//").concat(window.location.host) + res.responseText
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$('#msg').append(res.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formDataToObj(formArray) {
|
||||
let obj = {};
|
||||
for (let i = 0; i < formArray.length; i++){
|
||||
obj[formArray[i]['name']] = formArray[i]['value'];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
M.Tabs.init(document.getElementById("tab"));
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
65
templates/home.html
Normal file
65
templates/home.html
Normal file
@ -0,0 +1,65 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta title="Telegram transport">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-alpha.4/css/materialize.min.css">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-alpha.4/js/materialize.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row" style="margin-top: 5%">
|
||||
<form class="col s8 offset-s2" method="POST" id="save-crm" action="/create/">
|
||||
<p id="msg"></p>
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<input placeholder="API Url" id="api_url" name="api_url" type="text" class="validate">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<input placeholder="API Key" id="api_key" name="api_key" type="text" class="validate">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6 offset-s5">
|
||||
<button class="btn waves-effect waves-light red lighten-1" type="submit" name="action">Connect
|
||||
<i class="material-icons right">sync</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
$('#save-crm').on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
send($(this).attr('action'), $(this).serialize())
|
||||
});
|
||||
|
||||
function send(url, data) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
data: data,
|
||||
success: function(res){
|
||||
console.log(res);
|
||||
},
|
||||
error: function (res){
|
||||
if (res.status < 400) {
|
||||
if (res.responseText) {
|
||||
document.location.replace(
|
||||
location.protocol.concat("//").concat(window.location.host) + res.responseText
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$('#msg').append(res.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
17
token.go
Normal file
17
token.go
Normal file
@ -0,0 +1,17 @@
|
||||
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))))
|
||||
}
|
Loading…
Reference in New Issue
Block a user