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