From d54aae2e4b32c61912ad4fcc6f3bbfcdfd577777 Mon Sep 17 00:00:00 2001 From: Tit Petric Date: Tue, 2 Oct 2018 16:22:52 +0200 Subject: [PATCH] add(sam): migrations code start --- sam/db/migrate.go | 80 ++++++++++++++++++++++++++++++++++++++++++ sam/db/migrate_test.go | 30 ++++++++++++++++ sam/db/mysql/statik.go | 12 +++++++ 3 files changed, 122 insertions(+) create mode 100644 sam/db/migrate.go create mode 100644 sam/db/migrate_test.go create mode 100644 sam/db/mysql/statik.go diff --git a/sam/db/migrate.go b/sam/db/migrate.go new file mode 100644 index 000000000..3d9fd88b0 --- /dev/null +++ b/sam/db/migrate.go @@ -0,0 +1,80 @@ +package db + +import ( + "fmt" + "log" + "os" + "regexp" + "sort" + + _ "github.com/crusttech/crust/sam/db/mysql" + "github.com/pkg/errors" + "github.com/rakyll/statik/fs" + "github.com/titpetric/factory" +) + +//go:generate statik -p mysql -Z -f -src=schema/mysql + +func statements(contents []byte, err error) ([]string, error) { + if err != nil { + return []string{}, err + } + return regexp.MustCompilePOSIX(";$").Split(string(contents), -1), nil +} + +func Migrate(db *factory.DB) error { + statikFS, err := fs.New() + if err != nil { + return errors.Wrap(err, "Error creating statik filesystem") + } + + var files []string + + fs.Walk(statikFS, "/", func(filename string, info os.FileInfo, err error) error { + if len(filename) > 4 && filename[len(filename)-4:] == ".sql" { + files = append(files, filename) + } + return nil + }) + + sort.Strings(files) + + if len(files) == 0 { + return errors.New("No files encoded for migration, need at least one SQL file") + } + + // @todo: create table migrations by hand, log each filename status/date + /* + create table if not exists migrations ( + filename string, status [ok, fail], message string (may be error text?), stamp datetime + ) + */ + + for _, filename := range files { + // @todo: select * from migrations to check if (filename) was processed, skip if yes + + stmts, err := statements(fs.ReadFile(statikFS, filename)) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Error applying migration %s", filename)) + } + + up := func() error { + log.Println("Running migration for", filename) + for _, query := range stmts { + if _, err := db.Exec(query); err != nil { + return err + } + } + log.Println("Migration done OK") + + // @todo: insert/update into migrations with (filename) + return nil + } + + if err := db.Transaction(up); err != nil { + return err + } + } + + return nil +} diff --git a/sam/db/migrate_test.go b/sam/db/migrate_test.go new file mode 100644 index 000000000..7c76a5a5e --- /dev/null +++ b/sam/db/migrate_test.go @@ -0,0 +1,30 @@ +package db + +import ( + "testing" + + "github.com/joho/godotenv" + "github.com/namsral/flag" + "github.com/titpetric/factory" + + "github.com/crusttech/crust/internal/config" +) + +func TestMigrations(t *testing.T) { + // @todo this is a very optimistic initialization, make it more robust + godotenv.Load("../../.env") + + dbConfig := new(config.Database).Init("sam") + flag.Parse() + + if err := dbConfig.Validate(); err != nil { + t.Fatalf("Error in database config: %+v", err) + } + + factory.Database.Add("default", dbConfig.DSN) + + db := factory.Database.MustGet() + if err := Migrate(db); err != nil { + t.Fatalf("Unexpected error: %#v", err) + } +} diff --git a/sam/db/mysql/statik.go b/sam/db/mysql/statik.go new file mode 100644 index 000000000..98788f44f --- /dev/null +++ b/sam/db/mysql/statik.go @@ -0,0 +1,12 @@ +// Code generated by statik. DO NOT EDIT. + +package mysql + +import ( + "github.com/rakyll/statik/fs" +) + +func init() { + data := "PK\x03\x04\x14\x00\x08\x00\x00\x00WBAM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01g\xd8\xb1[-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known channels\nCREATE TABLE channels (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the channel\n topic TEXT NOT NULL,\n meta JSON NOT NULL,\n\n type ENUM ('private', 'public', 'group') NOT NULL DEFAULT 'public',\n\n rel_organisation BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_creator BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- channel soft delete\n\n rel_last_message BIGINT UNSIGNED NOT NULL DEFAULT 0,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- handles channel membership\nCREATE TABLE channel_members (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n type ENUM ('owner', 'member') NOT NULL DEFAULT 'member',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n\n PRIMARY KEY (rel_channel, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE channel_views (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n -- timestamp of last view, should be enough to find out which messaghr\n viewed_at DATETIME NOT NULL DEFAULT NOW(),\n\n -- new messages count since last view\n new_since INT UNSIGNED NOT NULL DEFAULT 0,\n\n PRIMARY KEY (rel_user, rel_channel)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE channel_pins (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (rel_channel, rel_message)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE messages (\n id BIGINT UNSIGNED NOT NULL,\n type TEXT,\n message TEXT NOT NULL,\n meta JSON,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n reply_to BIGINT UNSIGNED NULL REFERENCES messages(id),\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE reactions (\n id BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n reaction TEXT NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE attachments (\n id BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE message_attachment (\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_attachment BIGINT UNSIGNED NOT NULL REFERENCES attachment(id),\n\n PRIMARY KEY (rel_message)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE event_queue (\n id BIGINT UNSIGNED NOT NULL,\n origin BIGINT UNSIGNED NOT NULL,\n subscriber TEXT,\n payload JSON,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE event_queue_synced (\n origin BIGINT UNSIGNED NOT NULL,\n rel_last BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (origin)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08x\x16\x11\x1f\x94\x15\x00\x00\x94\x15\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00WBAMx\x16\x11\x1f\x94\x15\x00\x00\x94\x15\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01g\xd8\xb1[PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00Q\x00\x00\x00\xe5\x15\x00\x00\x00\x00" + fs.Register(data) +}