puerta/cmd/db/main.go

137 lines
3.0 KiB
Go

package db
import (
"embed"
"fmt"
"os"
"strings"
"time"
"git.rob.mx/nidito/chinampa/pkg/command"
"git.rob.mx/nidito/puerta/internal/server"
"github.com/sirupsen/logrus"
"github.com/upper/db/v4"
"github.com/upper/db/v4/adapter/sqlite"
"gopkg.in/yaml.v3"
)
//go:embed migrations/*
var migrationsDir embed.FS
const baseMigration = "0000-00-00-base.sql"
func runMigration(sess db.Session, path string) error {
contents, err := migrationsDir.ReadFile("migrations/" + path)
if err != nil {
return err
}
q := fmt.Sprintf("%s", contents)
logrus.Infof("running migration\n %s", q)
return sess.Tx(func(sess db.Session) error {
q += `
;
INSERT INTO migrations (name, applied) VALUES (?, ?);
`
_, err = sess.SQL().Exec(q, path, time.Now().UTC().Format(time.RFC3339))
return err
})
}
var MigrationsCommand = &command.Command{
Path: []string{"db", "migrate"},
Summary: "Runs database migrations",
Description: "",
Options: command.Options{
"config": {
Type: "string",
Default: "./config.joao.yaml",
},
"db": {
Type: "string",
Default: "./puerta.db",
},
},
Action: func(cmd *command.Command) error {
config := cmd.Options["config"].ToValue().(string)
dbPath := cmd.Options["db"].ToValue().(string)
data, err := os.ReadFile(config)
if err != nil {
return fmt.Errorf("could not read config file: %w", err)
}
cfg := server.ConfigDefaults(dbPath)
if err := yaml.Unmarshal(data, &cfg); err != nil {
return fmt.Errorf("could not unserialize yaml at %s: %w", config, err)
}
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{DisableTimestamp: false})
sess, err := sqlite.Open(sqlite.ConnectionURL{
Database: cfg.DB,
Options: map[string]string{
"_journal": "WAL",
"_busy_timeout": "5000",
},
})
if err != nil {
return err
}
defer sess.Close()
cols, err := sess.Collections()
if err != nil {
return err
}
needsInitialMigration := true
for _, col := range cols {
if col.Name() == "migrations" {
logger.Infof("found migrations table: %s", col.Name())
needsInitialMigration = false
break
}
}
if needsInitialMigration {
logger.Info("Running initial migration")
if err := runMigration(sess, baseMigration); err != nil {
logger.Fatalf("Could not run base migration: %s", err)
}
}
migrations, err := migrationsDir.ReadDir("migrations")
if err != nil {
return err
}
for _, mig := range migrations {
name := mig.Name()
if strings.HasSuffix(name, ".sql") && mig.Type().IsRegular() && name != baseMigration {
cnt, err := sess.Collection("migrations").Find(db.Cond{"name": name}).Count()
if err != nil {
return fmt.Errorf("Could not count migrations for %s: %s", name, err)
}
if cnt > 0 {
logger.Infof("Already applied %s", name)
continue
}
logger.Infof("Running migration: %s", name)
if err := runMigration(sess, name); err != nil {
logger.Fatalf("Could not run base migration: %s", err)
}
}
}
return nil
},
}