3
0

import crm migrations

This commit is contained in:
Tit Petric
2018-10-08 12:28:33 +00:00
parent 0f9f2e08f9
commit f736c1e945
6 changed files with 194 additions and 14 deletions

109
crm/db/migrate.go Normal file
View File

@@ -0,0 +1,109 @@
package db
import (
"fmt"
"log"
"os"
"strings"
"path/filepath"
"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
}
type migration struct {
Project string
Filename string
StatementIndex int
Status string
}
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 {
matched, err := filepath.Match("*.up.sql", filename)
if err == nil && matched {
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")
}
migrate := func(filename string, useLog bool) error {
status := migration{
Project: "crm",
Filename: filename,
}
if useLog {
if err := db.Get(&status, "select * from migrations where project=? and filename=?", status.Project, status.Filename); err != nil {
return err
}
if status.Status == "ok" {
return nil
}
}
up := func() error {
stmts, err := statements(fs.ReadFile(statikFS, filename))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Error reading migration %s", filename))
}
log.Println("Running migration for", filename)
for idx, query := range stmts {
if strings.TrimSpace(query) != "" && idx >= status.StatementIndex {
status.StatementIndex = idx
if _, err := db.Exec(query); err != nil {
return err
}
}
}
log.Println("Migration done OK")
status.Status = "ok"
return nil
}
if err := db.Transaction(up); err != nil {
status.Status = err.Error()
if useLog {
db.Replace("migrations", status)
}
return err
}
return nil
}
if err := migrate("/migration.sql", false); err != nil {
return err
}
for _, filename := range files {
migrate(filename, true)
}
return nil
}

42
crm/db/migrate_test.go Normal file
View File

@@ -0,0 +1,42 @@
package db
import (
"log"
"context"
"testing"
"time"
"github.com/titpetric/dockertest"
"github.com/titpetric/factory"
)
func TestMigrations(t *testing.T) {
args := []string{
// "--rm",
"-e", "MYSQL_ROOT_PASSWORD=root",
"-e", "MYSQL_DATABASE=test",
}
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
count := 0
mysql, err := dockertest.RunContainerContext(ctx, "titpetric/percona-xtrabackup", "3306", func(addr string) error {
factory.Database.Add("default", "root:root@tcp("+addr+")/test?collation=utf8mb4_general_ci")
log.Println(addr)
_, err := factory.Database.Get()
count++
time.Sleep(time.Second)
return err
}, args...)
defer mysql.Terminate() // Shutdown()
if err != nil {
t.Fatalf("Error starting mysql: %#v", err)
}
db := factory.Database.MustGet()
if err := Migrate(db); err != nil {
t.Fatalf("Unexpected error: %#v", err)
}
}

12
crm/db/mysql/statik.go Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,20 @@
CREATE TABLE `crm_content` (
`id` bigint(20) unsigned NOT NULL,
`module_id` bigint(20) unsigned NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT NULL,
`archived_at` datetime DEFAULT NULL,
`deleted_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`,`module_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `crm_content_columns` (
`content_id` bigint(20) NOT NULL,
`column_name` varchar(255) NOT NULL,
`column_value` text NOT NULL,
PRIMARY KEY (`content_id`,`column_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `crm_fields` (
`field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',
`field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',
@@ -12,13 +29,6 @@ CREATE TABLE `crm_module` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `crm_module_content` (
`id` bigint(20) unsigned NOT NULL,
`module_id` bigint(20) unsigned NOT NULL,
`json` json NOT NULL COMMENT 'Entry JSON blob based on module fields list',
PRIMARY KEY (`id`,`module_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `crm_module_form` (
`module_id` bigint(20) unsigned NOT NULL,
`place` tinyint(3) unsigned NOT NULL,
@@ -29,10 +39,3 @@ CREATE TABLE `crm_module_form` (
PRIMARY KEY (`module_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `crm_fields` VALUES ('bool','Boolean value (yes / no)','');
INSERT INTO `crm_fields` VALUES ('email','E-mail input','');
INSERT INTO `crm_fields` VALUES ('enum','Single option picker','');
INSERT INTO `crm_fields` VALUES ('hidden','Hidden value','');
INSERT INTO `crm_fields` VALUES ('stamp','Date/time input','');
INSERT INTO `crm_fields` VALUES ('text','Text input','');
INSERT INTO `crm_fields` VALUES ('textarea','Text input (multi-line)','');

View File

@@ -0,0 +1,7 @@
INSERT INTO `crm_fields` VALUES ('bool','Boolean value (yes / no)','');
INSERT INTO `crm_fields` VALUES ('email','E-mail input','');
INSERT INTO `crm_fields` VALUES ('enum','Single option picker','');
INSERT INTO `crm_fields` VALUES ('hidden','Hidden value','');
INSERT INTO `crm_fields` VALUES ('stamp','Date/time input','');
INSERT INTO `crm_fields` VALUES ('text','Text input','');
INSERT INTO `crm_fields` VALUES ('textarea','Text input (multi-line)','');

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `migrations` (
`project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',
`filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',
`statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',
`status` varchar(16) NOT NULL COMMENT 'ok or full error message'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;