From f86c903a70444e1bd35f6599fa9ae0d19b6ea664 Mon Sep 17 00:00:00 2001 From: Boris Ershov Date: Thu, 15 Jun 2023 17:38:56 +0700 Subject: [PATCH] Init code --- .gitignore | 3 + README.md | 71 +++++++++++ ctx/args.go | 72 +++++++++++ ctx/conf.go | 51 ++++++++ ctx/context.go | 105 ++++++++++++++++ ds/mysql/dst_feedback_issue.go | 61 ++++++++++ ds/mysql/dst_issues_banch.go | 63 ++++++++++ ds/mysql/dst_user.go | 63 ++++++++++ ds/mysql/mysql.go | 52 ++++++++ ds/mysql/src_id.go | 26 ++++ ds/mysql/src_issues.go | 26 ++++ ds/redis/redis.go | 39 ++++++ ds/redis/src_presale_issues.go | 43 +++++++ go.mod | 28 +++++ go.sum | 129 ++++++++++++++++++++ main.go | 48 ++++++++ misc/errors.go | 7 ++ modules/dsmigrate/dsmigrate.go | 213 +++++++++++++++++++++++++++++++++ routines/migrate/migrate.go | 36 ++++++ 19 files changed, 1136 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ctx/args.go create mode 100644 ctx/conf.go create mode 100644 ctx/context.go create mode 100644 ds/mysql/dst_feedback_issue.go create mode 100644 ds/mysql/dst_issues_banch.go create mode 100644 ds/mysql/dst_user.go create mode 100644 ds/mysql/mysql.go create mode 100644 ds/mysql/src_id.go create mode 100644 ds/mysql/src_issues.go create mode 100644 ds/redis/redis.go create mode 100644 ds/redis/src_presale_issues.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 misc/errors.go create mode 100644 modules/dsmigrate/dsmigrate.go create mode 100644 routines/migrate/migrate.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8bc289 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.project +/.vscode +/.tmp diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0e4016 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Nixys Support Bot Migrate + +This tool helps you to migrate the DB from latest version of [nxs-chat-srv](https://github.com/nixys/nxs-chat-srv) to [Nixys Support Bot](github.com/nixys/nxs-support-bot) v1.0.0. + +In text below lets use following conventions: +- [nxs-chat-srv](https://github.com/nixys/nxs-chat-srv) DB it's an `old` DB +- [Nixys Support Bot](github.com/nixys/nxs-support-bot) DB it's a `new` DB + +What this tool do: +- Cleans up a data in `new` DB +- Migrates necessary data from `old` MySQL to `new` +- Migrates necessary data from `old` Redis to `new` +- While migration all incorrect data from `old` to `new` will be skipped + +## Quickstart + +### Install + +This app is a helper for [Nixys Support Bot](github.com/nixys/nxs-support-bot). Please see Nixys Support Bot readme for installation. + +### Settings + +#### General settings + +| Option | Type | Required | Default value | Description | +|--- | :---: | :---: | :---: |--- | +| `logfile` | String | No | `stdout` | Log file path. Also you may use `stdout` and `stderr` | +| `loglevel` | String | No | `info` | Log level. Available values: `debug`, `warn`, `error` and `info` | +| `pidfile` | String | No | - | Pid file path. If `pidfile` is not set it will not be created | +| `src` | [Src](#src-settings) | Yes | - | Source (`old`) databases settings | +| `dst` | [Dst](#dst-settings) | Yes | - | Destination (`new`) databases settings | + +##### Src settings + +| Option | Type | Required | Default value | Description | +|--- | :---: | :---: | :---: |--- | +| `mysql` | [MySQL](#mysql-settings) | Yes | - | MySQL settings | +| `redis` | [Redis](#redis-settings) | Yes | - | Redis settings | + +##### Dst settings + +| Option | Type | Required | Default value | Description | +|--- | :---: | :---: | :---: |--- | +| `mysql` | [MySQL](#mysql-settings) | Yes | - | MySQL settings | + +##### MySQL settings + +| Option | Type | Required | Default value | Description | +|--- | :---: | :---: | :---: |--- | +| `host` | String | Yes | - | Host to connect | +| `port` | Int | Yes | - | Port to connect | +| `db` | String | Yes | - | DB name to connect | +| `user` | String | Yes | - | User to connect | +| `password` | String | Yes | - | Password to connect | + +##### Redis settings + +| Option | Type | Required | Default value | Description | +|--- | :---: | :---: | :---: |--- | +| `host` | String | Yes | - | Host to connect | +| `port` | Int | Yes | - | Port to connect | + +## Feedback + +For support and feedbak please contact me: +- telegram: [@borisershov](https://t.me/borisershov) +- e-mail: b.ershov@nixys.ru + +## License + +Nixys Support Bot Migrate tool is released under the [GPLv3](LICENSE). diff --git a/ctx/args.go b/ctx/args.go new file mode 100644 index 0000000..70150c6 --- /dev/null +++ b/ctx/args.go @@ -0,0 +1,72 @@ +package ctx + +import ( + "fmt" + "os" + + "github.com/pborman/getopt/v2" +) + +const ( + confPathDefault = "nxs-support-bot-migrate.conf" +) + +// Args contains arguments value read from command line +type Args struct { + ConfigPath string +} + +// ArgsRead reads arguments from command line +func ArgsRead() Args { + + var a Args + + args := getopt.New() + + helpFlag := args.BoolLong( + "help", + 'h', + "Show help") + + versionFlag := args.BoolLong( + "version", + 'v', + "Show program version") + + confPath := args.StringLong( + "conf", + 'c', + "", + "Config file path") + + args.Parse(os.Args) + + /* Show help */ + if *helpFlag == true { + argsHelp(args) + os.Exit(0) + } + + /* Show version */ + if *versionFlag == true { + argsVersion() + os.Exit(0) + } + + /* Config path */ + if args.IsSet("conf") == true { + a.ConfigPath = *confPath + } else { + a.ConfigPath = confPathDefault + } + + return a +} + +func argsHelp(args *getopt.Set) { + args.PrintUsage(os.Stdout) +} + +func argsVersion() { + fmt.Println("Not specified") +} diff --git a/ctx/conf.go b/ctx/conf.go new file mode 100644 index 0000000..86d4443 --- /dev/null +++ b/ctx/conf.go @@ -0,0 +1,51 @@ +package ctx + +import ( + conf "github.com/nixys/nxs-go-conf" +) + +type confOpts struct { + LogFile string `conf:"logfile" conf_extraopts:"default=stdout"` + LogLevel string `conf:"loglevel" conf_extraopts:"default=info"` + PidFile string `conf:"pidfile"` + Src srcConf `conf:"src" conf_extraopts:"required"` + Dst dstConf `conf:"dst" conf_extraopts:"required"` +} + +type srcConf struct { + MySQL mysqlConf `conf:"mysql" conf_extraopts:"required"` + Redis redisConf `conf:"redis" conf_extraopts:"required"` +} + +type dstConf struct { + MySQL mysqlConf `conf:"mysql" conf_extraopts:"required"` +} + +type mysqlConf struct { + Host string `conf:"host" conf_extraopts:"required"` + Port int `conf:"port" conf_extraopts:"required"` + DB string `conf:"db" conf_extraopts:"required"` + User string `conf:"user" conf_extraopts:"required"` + Password string `conf:"password" conf_extraopts:"required"` +} + +type redisConf struct { + Host string `conf:"host" conf_extraopts:"required"` + Port int `conf:"port" conf_extraopts:"required"` +} + +func confRead(confPath string) (confOpts, error) { + + var c confOpts + + err := conf.Load(&c, conf.Settings{ + ConfPath: confPath, + ConfType: conf.ConfigTypeYAML, + UnknownDeny: true, + }) + if err != nil { + return c, err + } + + return c, err +} diff --git a/ctx/context.go b/ctx/context.go new file mode 100644 index 0000000..ce62513 --- /dev/null +++ b/ctx/context.go @@ -0,0 +1,105 @@ +package ctx + +import ( + "fmt" + + "github.com/nixys/nxs-support-bot-migrate/ds/mysql" + "github.com/nixys/nxs-support-bot-migrate/ds/redis" + + appctx "github.com/nixys/nxs-go-appctx/v2" +) + +// Ctx defines application custom context +type Ctx struct { + Conf confOpts + Src SrcCtx + Dst DstCtx +} + +type SrcCtx struct { + MySQL mysql.MySQL + Redis redis.Redis +} + +type DstCtx struct { + MySQL mysql.MySQL +} + +// Init initiates application custom context +func (c *Ctx) Init(opts appctx.CustomContextFuncOpts) (appctx.CfgData, error) { + + //a := opts.Args.(*Args) + + // Read config file + conf, err := confRead(opts.Config) + if err != nil { + return appctx.CfgData{}, err + } + + // Set application context + c.Conf = conf + + redisHost := fmt.Sprintf("%s:%d", c.Conf.Src.Redis.Host, c.Conf.Src.Redis.Port) + + // Connect to source MySQL + c.Src.MySQL, err = mysql.Connect(mysql.Settings{ + Host: c.Conf.Src.MySQL.Host, + Port: c.Conf.Src.MySQL.Port, + Database: c.Conf.Src.MySQL.DB, + User: c.Conf.Src.MySQL.User, + Password: c.Conf.Src.MySQL.Password, + }) + if err != nil { + return appctx.CfgData{}, err + } + + // Connect to source Redis + c.Src.Redis, err = redis.Connect(redisHost) + if err != nil { + return appctx.CfgData{}, err + } + + // Connect to destination MySQL + c.Dst.MySQL, err = mysql.Connect(mysql.Settings{ + Host: c.Conf.Dst.MySQL.Host, + Port: c.Conf.Dst.MySQL.Port, + Database: c.Conf.Dst.MySQL.DB, + User: c.Conf.Dst.MySQL.User, + Password: c.Conf.Dst.MySQL.Password, + }) + if err != nil { + return appctx.CfgData{}, err + } + + return appctx.CfgData{ + LogFile: c.Conf.LogFile, + LogLevel: c.Conf.LogLevel, + PidFile: c.Conf.PidFile, + }, nil +} + +// Reload reloads application custom context +func (c *Ctx) Reload(opts appctx.CustomContextFuncOpts) (appctx.CfgData, error) { + + opts.Log.Debug("reloading context") + + c.Src.MySQL.Close() + c.Src.Redis.Close() + + c.Dst.MySQL.Close() + + return c.Init(opts) +} + +// Free frees application custom context +func (c *Ctx) Free(opts appctx.CustomContextFuncOpts) int { + + opts.Log.Debug("freeing context") + + c.Src.MySQL.Close() + c.Src.Redis.Close() + + c.Dst.MySQL.Close() + + return 0 +} diff --git a/ds/mysql/dst_feedback_issue.go b/ds/mysql/dst_feedback_issue.go new file mode 100644 index 0000000..0624cb5 --- /dev/null +++ b/ds/mysql/dst_feedback_issue.go @@ -0,0 +1,61 @@ +package mysql + +const dstFeedbackIssuesTableName = "feedback_issues" + +type DstFeedbackIssue struct { + TgUserID int64 `gorm:"column:tlgrm_userid"` + RdmnIssueID int64 `gorm:"column:rdmn_issue_id"` +} + +type DstFeedbackIssueInsertData struct { + TgUserID int64 `gorm:"column:tlgrm_userid"` + RdmnIssueID int64 `gorm:"column:rdmn_issue_id"` +} + +func (DstFeedbackIssue) TableName() string { + return dstFeedbackIssuesTableName +} + +func (DstFeedbackIssueInsertData) TableName() string { + return dstFeedbackIssuesTableName +} + +func (m *MySQL) DstFeedbackIssuesSave(issues []DstFeedbackIssueInsertData) error { + + if len(issues) == 0 { + return nil + } + + r := m.client. + Create(&issues) + if r.Error != nil { + return r.Error + } + + return nil +} + +func (m *MySQL) DstFeedbackIssuesGet() ([]DstFeedbackIssue, error) { + + issues := []DstFeedbackIssue{} + + r := m.client. + Find(&issues) + if r.Error != nil { + return nil, r.Error + } + + return issues, nil +} + +func (m *MySQL) DstFeedbackIssuesDeleteAll() error { + + r := m.client. + Where("1 = 1"). + Delete(DstFeedbackIssue{}) + if r.Error != nil { + return r.Error + } + + return nil +} diff --git a/ds/mysql/dst_issues_banch.go b/ds/mysql/dst_issues_banch.go new file mode 100644 index 0000000..da26e34 --- /dev/null +++ b/ds/mysql/dst_issues_banch.go @@ -0,0 +1,63 @@ +package mysql + +const dstIssuesBanchTableName = "issues_banch" + +type DstIssueBanch struct { + TgChatID int64 `gorm:"column:tg_chat_id"` + TgMessageID int64 `gorm:"column:tg_message_id"` + RdmnIssueID int64 `gorm:"column:rdmn_issue_id"` +} + +type DstIssueBanchInsertData struct { + TgChatID int64 `gorm:"column:tg_chat_id"` + TgMessageID int64 `gorm:"column:tg_message_id"` + RdmnIssueID int64 `gorm:"column:rdmn_issue_id"` +} + +func (DstIssueBanch) TableName() string { + return dstIssuesBanchTableName +} + +func (DstIssueBanchInsertData) TableName() string { + return dstIssuesBanchTableName +} + +func (m *MySQL) DstIssuesBanchSave(issues []DstIssueBanchInsertData) error { + + if len(issues) == 0 { + return nil + } + + r := m.client. + Create(&issues) + if r.Error != nil { + return r.Error + } + + return nil +} + +func (m *MySQL) DstIssuesBanchGet() ([]DstIssueBanch, error) { + + ibs := []DstIssueBanch{} + + r := m.client. + Find(&ibs) + if r.Error != nil { + return nil, r.Error + } + + return ibs, nil +} + +func (m *MySQL) DstIssuesBanchDeleteAll() error { + + r := m.client. + Where("1 = 1"). + Delete(DstIssueBanch{}) + if r.Error != nil { + return r.Error + } + + return nil +} diff --git a/ds/mysql/dst_user.go b/ds/mysql/dst_user.go new file mode 100644 index 0000000..3c177a9 --- /dev/null +++ b/ds/mysql/dst_user.go @@ -0,0 +1,63 @@ +package mysql + +const dstUsersTableName = "users" + +type DstUser struct { + TgUserID int64 `gorm:"column:tlgrm_userid"` + RdmnUserID int64 `gorm:"column:rdmn_userid"` + Lang string `gorm:"column:lang"` +} + +type DstUserInsertData struct { + TgUserID int64 `gorm:"column:tlgrm_userid"` + RdmnUserID int64 `gorm:"column:rdmn_userid"` + Lang string `gorm:"column:lang"` +} + +func (DstUser) TableName() string { + return dstUsersTableName +} + +func (DstUserInsertData) TableName() string { + return dstUsersTableName +} + +func (m *MySQL) DstUsersSave(users []DstUserInsertData) error { + + if len(users) == 0 { + return nil + } + + r := m.client. + Create(&users) + if r.Error != nil { + return r.Error + } + + return nil +} + +func (m *MySQL) DstUsersGet() ([]DstUser, error) { + + users := []DstUser{} + + r := m.client. + Find(&users) + if r.Error != nil { + return nil, r.Error + } + + return users, nil +} + +func (m *MySQL) DstUsersDeleteAll() error { + + r := m.client. + Where("1 = 1"). + Delete(DstUser{}) + if r.Error != nil { + return r.Error + } + + return nil +} diff --git a/ds/mysql/mysql.go b/ds/mysql/mysql.go new file mode 100644 index 0000000..37f8f97 --- /dev/null +++ b/ds/mysql/mysql.go @@ -0,0 +1,52 @@ +package mysql + +import ( + "fmt" + + gmysql "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// MySQL it is a MySQL module context structure +type MySQL struct { + client *gorm.DB +} + +// Settings contains settings for MySQL +type Settings struct { + Host string + Port int + User string + Password string + Database string +} + +// Connect connects to MySQL +func Connect(s Settings) (MySQL, error) { + + client, err := gorm.Open(gmysql.Open( + fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + s.User, + s.Password, + s.Host, + s.Port, + s.Database)), + &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }, + ) + if err != nil { + return MySQL{}, err + } + + return MySQL{ + client: client, + }, nil +} + +// Close closes MySQL connection +func (m *MySQL) Close() error { + db, _ := m.client.DB() + return db.Close() +} diff --git a/ds/mysql/src_id.go b/ds/mysql/src_id.go new file mode 100644 index 0000000..a744dad --- /dev/null +++ b/ds/mysql/src_id.go @@ -0,0 +1,26 @@ +package mysql + +const srcIDsTableName = "ids" + +type SrcID struct { + TgUserID int64 `gorm:"column:tlgrm_userid"` + TgUserName string `gorm:"column:tlgrm_username"` + RdmnUserID int64 `gorm:"column:rdmn_userid"` +} + +func (SrcID) TableName() string { + return srcIDsTableName +} + +func (m *MySQL) SrcIDsGet() ([]SrcID, error) { + + ids := []SrcID{} + + r := m.client. + Find(&ids) + if r.Error != nil { + return nil, r.Error + } + + return ids, nil +} diff --git a/ds/mysql/src_issues.go b/ds/mysql/src_issues.go new file mode 100644 index 0000000..50fc16c --- /dev/null +++ b/ds/mysql/src_issues.go @@ -0,0 +1,26 @@ +package mysql + +const srcIssuesTableName = "issues" + +type SrcIssue struct { + TgChatID int64 `gorm:"column:tlgrm_chat_id"` + TgMessageID int64 `gorm:"column:tlgrm_message_id"` + RdmnIssueID int64 `gorm:"column:rdmn_issue_id"` +} + +func (SrcIssue) TableName() string { + return srcIssuesTableName +} + +func (m *MySQL) SrcIssuesGet() ([]SrcIssue, error) { + + issues := []SrcIssue{} + + r := m.client. + Find(&issues) + if r.Error != nil { + return nil, r.Error + } + + return issues, nil +} diff --git a/ds/redis/redis.go b/ds/redis/redis.go new file mode 100644 index 0000000..e53810c --- /dev/null +++ b/ds/redis/redis.go @@ -0,0 +1,39 @@ +package redis + +import ( + "time" + + "github.com/go-redis/redis" +) + +type Redis struct { + client *redis.Client +} + +func Connect(host string) (Redis, error) { + + var r Redis + + client := redis.NewClient(&redis.Options{ + Addr: host, + DialTimeout: 10 * time.Second, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + PoolSize: 10, + PoolTimeout: 30 * time.Second, + }) + + p := client.Ping() + + if p.Err() != nil { + return r, p.Err() + } + + r.client = client + + return r, nil +} + +func (r *Redis) Close() error { + return r.client.Close() +} diff --git a/ds/redis/src_presale_issues.go b/ds/redis/src_presale_issues.go new file mode 100644 index 0000000..1aa72ff --- /dev/null +++ b/ds/redis/src_presale_issues.go @@ -0,0 +1,43 @@ +package redis + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/nixys/nxs-support-bot-migrate/misc" + + "github.com/go-redis/redis" +) + +const srcPresaleIssuesKey = "nxs-chat-srv:nixys:presale" + +func (r *Redis) SrcPresalesGet() (map[int64]int64, error) { + + iss := make(map[int64]int64) + + irs := r.client.HGetAll(srcPresaleIssuesKey) + if irs.Err() != nil { + if irs.Err() == redis.Nil { + // Empty keys + return nil, fmt.Errorf("redis src presale issues get: %w", misc.ErrNotFound) + } + return nil, fmt.Errorf("redis src presale issues get: %w", irs.Err()) + } + + for k, val := range irs.Val() { + + key, err := strconv.ParseInt(k, 10, 64) + if err != nil { + return nil, fmt.Errorf("redis src presale issues get: %w", err) + } + + var v int64 + if err := json.Unmarshal([]byte(val), &v); err != nil { + return nil, fmt.Errorf("redis src presale issues get: %w", err) + } + iss[key] = v + } + + return iss, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..796b8a8 --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module github.com/nixys/nxs-support-bot-migrate + +go 1.19 + +require ( + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-sql-driver/mysql v1.6.0 + github.com/nixys/nxs-go-appctx/v2 v2.0.0 + github.com/nixys/nxs-go-conf v1.0.1 + github.com/pborman/getopt/v2 v2.1.0 + github.com/sirupsen/logrus v1.8.1 + gorm.io/driver/mysql v1.3.4 + gorm.io/gorm v1.23.7 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.4 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.27.6 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect + github.com/stretchr/testify v1.7.1 // indirect + golang.org/x/sys v0.6.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2a12f23 --- /dev/null +++ b/go.sum @@ -0,0 +1,129 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nixys/nxs-go-appctx/v2 v2.0.0 h1:t5zLCcT9y8SqTm5OhnTQlRMQmOWNE+KNbAU36efrX44= +github.com/nixys/nxs-go-appctx/v2 v2.0.0/go.mod h1:kw7Z83tJKiGrZ3cWp0wShe+e1wod6QEeJAaYxx/1Fq0= +github.com/nixys/nxs-go-conf v1.0.1 h1:yR2KwNx1CRy8yH7xgr1P6aGNAN6G/5WR06uZIS1EyBc= +github.com/nixys/nxs-go-conf v1.0.1/go.mod h1:m2zxhmA7YfWdFdmTQZSqP0+C16oRUztfXss+yhbScgY= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA= +github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gorm.io/driver/mysql v1.3.4 h1:/KoBMgsUHC3bExsekDcmNYaBnfH2WNeFuXqqrqMc98Q= +gorm.io/driver/mysql v1.3.4/go.mod h1:s4Tq0KmD0yhPGHbZEwg1VPlH0vT/GBHJZorPzhcxBUE= +gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.7 h1:ww+9Mu5WwHKDSOQZFC4ipu/sgpKMr9EtrJ0uwBqNtB0= +gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a8a9a37 --- /dev/null +++ b/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + "os" + "syscall" + + "github.com/nixys/nxs-support-bot-migrate/ctx" + "github.com/nixys/nxs-support-bot-migrate/routines/migrate" + + appctx "github.com/nixys/nxs-go-appctx/v2" + + _ "github.com/go-sql-driver/mysql" + "github.com/sirupsen/logrus" +) + +func main() { + + // Read command line arguments + args := ctx.ArgsRead() + + // Init appctx + appCtx, err := appctx.ContextInit(appctx.Settings{ + CustomContext: &ctx.Ctx{}, + Args: &args, + CfgPath: args.ConfigPath, + TermSignals: []os.Signal{syscall.SIGTERM, syscall.SIGINT}, + ReloadSignals: []os.Signal{syscall.SIGHUP}, + LogrotateSignals: []os.Signal{syscall.SIGUSR1}, + LogFormatter: &logrus.JSONFormatter{}, + }) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + appCtx.Log().Info("program started") + + // main() body function + defer appCtx.MainBodyGeneric() + + // Create main context + c := context.Background() + + // Create updater routine + appCtx.RoutineCreate(c, migrate.Runtime) +} diff --git a/misc/errors.go b/misc/errors.go new file mode 100644 index 0000000..244d91b --- /dev/null +++ b/misc/errors.go @@ -0,0 +1,7 @@ +package misc + +import "errors" + +var ( + ErrNotFound = errors.New("entity not found") +) diff --git a/modules/dsmigrate/dsmigrate.go b/modules/dsmigrate/dsmigrate.go new file mode 100644 index 0000000..92a3403 --- /dev/null +++ b/modules/dsmigrate/dsmigrate.go @@ -0,0 +1,213 @@ +package dsmigrate + +import ( + "fmt" + + "github.com/nixys/nxs-support-bot-migrate/ds/mysql" + "github.com/nixys/nxs-support-bot-migrate/ds/redis" +) + +type Settings struct { + Src SrcSettings + Dst DstSettings +} + +type SrcSettings struct { + MySQL mysql.MySQL + Redis redis.Redis +} + +type DstSettings struct { + MySQL mysql.MySQL +} + +type Migrate struct { + s srcCtx + d dstCtx +} + +type srcCtx struct { + m mysql.MySQL + r redis.Redis +} + +type dstCtx struct { + m mysql.MySQL +} + +func Init(s Settings) Migrate { + + return Migrate{ + s: srcCtx{ + m: s.Src.MySQL, + r: s.Src.Redis, + }, + d: dstCtx{ + s.Dst.MySQL, + }, + } +} + +type user struct { + tgID int64 + rdmnID int64 +} + +const langDefault = "en" + +func (m *Migrate) Migrate() error { + + if err := m.users(); err != nil { + return fmt.Errorf("migrate: %w", err) + } + + if err := m.issues(); err != nil { + return fmt.Errorf("migrate: %w", err) + } + + if err := m.feedbackIssues(); err != nil { + return fmt.Errorf("migrate: %w", err) + } + + return nil +} + +func (m *Migrate) users() error { + + if err := m.d.m.DstUsersDeleteAll(); err != nil { + return fmt.Errorf("migrate users: %w", err) + } + + srcUsers, err := m.s.m.SrcIDsGet() + if err != nil { + return fmt.Errorf("migrate users: %w", err) + } + + users := []mysql.DstUserInsertData{} + for _, usr := range srcUsers { + + // Skip incorrect data + if usr.TgUserID == 0 || len(usr.TgUserName) == 0 || usr.RdmnUserID == 0 { + continue + } + + if len(users)%100 == 0 { + if err := m.d.m.DstUsersSave(users); err != nil { + return fmt.Errorf("migrate users: %w", err) + } + users = []mysql.DstUserInsertData{} + } + + users = append( + users, + mysql.DstUserInsertData{ + TgUserID: usr.TgUserID, + RdmnUserID: usr.RdmnUserID, + Lang: langDefault, + }, + ) + } + + if len(users) > 0 { + if err := m.d.m.DstUsersSave(users); err != nil { + return fmt.Errorf("migrate users: %w", err) + } + } + + return nil +} + +func (m *Migrate) issues() error { + + if err := m.d.m.DstIssuesBanchDeleteAll(); err != nil { + return fmt.Errorf("migrate issues: %w", err) + } + + srcIssues, err := m.s.m.SrcIssuesGet() + if err != nil { + return fmt.Errorf("migrate issues: %w", err) + } + + // Delete duplicates (if exist) + srcIssuesClean := make(map[string]mysql.SrcIssue) + for _, i := range srcIssues { + srcIssuesClean[fmt.Sprintf("%d-%d-%d", i.TgChatID, i.TgMessageID, i.RdmnIssueID)] = i + } + + diss := []mysql.DstIssueBanchInsertData{} + for _, i := range srcIssuesClean { + + // Skip incorrect data + if i.TgChatID == 0 || i.TgMessageID == 0 || i.RdmnIssueID == 0 { + continue + } + + if len(diss)%100 == 0 { + if err := m.d.m.DstIssuesBanchSave(diss); err != nil { + return fmt.Errorf("migrate issues: %w", err) + } + diss = []mysql.DstIssueBanchInsertData{} + } + + diss = append( + diss, + mysql.DstIssueBanchInsertData{ + TgChatID: i.TgChatID, + TgMessageID: i.TgMessageID, + RdmnIssueID: i.RdmnIssueID, + }, + ) + } + + if len(diss) > 0 { + if err := m.d.m.DstIssuesBanchSave(diss); err != nil { + return fmt.Errorf("migrate issues: %w", err) + } + } + + return nil +} + +func (m *Migrate) feedbackIssues() error { + + if err := m.d.m.DstFeedbackIssuesDeleteAll(); err != nil { + return fmt.Errorf("migrate feedback issues: %w", err) + } + + iss, err := m.s.r.SrcPresalesGet() + if err != nil { + return fmt.Errorf("migrate feedback issues: %w", err) + } + + issues := []mysql.DstFeedbackIssueInsertData{} + for k, v := range iss { + + // Skip incorrect data + if k == 0 || v == 0 { + continue + } + + if len(issues)%100 == 0 { + if err := m.d.m.DstFeedbackIssuesSave(issues); err != nil { + return fmt.Errorf("migrate feedback issues: %w", err) + } + issues = []mysql.DstFeedbackIssueInsertData{} + } + + issues = append( + issues, + mysql.DstFeedbackIssueInsertData{ + TgUserID: k, + RdmnIssueID: v, + }, + ) + } + + if len(issues) > 0 { + if err := m.d.m.DstFeedbackIssuesSave(issues); err != nil { + return fmt.Errorf("migrate feedback issues: %w", err) + } + } + + return nil +} diff --git a/routines/migrate/migrate.go b/routines/migrate/migrate.go new file mode 100644 index 0000000..eb89b76 --- /dev/null +++ b/routines/migrate/migrate.go @@ -0,0 +1,36 @@ +package migrate + +import ( + "context" + + "github.com/nixys/nxs-support-bot-migrate/ctx" + "github.com/nixys/nxs-support-bot-migrate/modules/dsmigrate" + + appctx "github.com/nixys/nxs-go-appctx/v2" +) + +// Runtime executes the routine +func Runtime(cr context.Context, appCtx *appctx.AppContext, crc chan interface{}) { + + cc := appCtx.CustomCtx().(*ctx.Ctx) + + de := dsmigrate.Init( + dsmigrate.Settings{ + Src: dsmigrate.SrcSettings{ + MySQL: cc.Src.MySQL, + Redis: cc.Src.Redis, + }, + Dst: dsmigrate.DstSettings{ + MySQL: cc.Dst.MySQL, + }, + }, + ) + + if err := de.Migrate(); err != nil { + appCtx.Log().Errorf(err.Error()) + appCtx.RoutineDoneSend(appctx.ExitStatusFailure) + return + } + + appCtx.RoutineDoneSend(appctx.ExitStatusSuccess) +}