Junie: TOTP flow update and db migrations.
This commit is contained in:
184
cmd/mcias/migrate.go
Normal file
184
cmd/mcias/migrate.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
migrationsDir string
|
||||
steps int
|
||||
)
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Manage database migrations",
|
||||
Long: `Commands for managing database migrations in the MCIAS system.`,
|
||||
}
|
||||
|
||||
var migrateUpCmd = &cobra.Command{
|
||||
Use: "up [steps]",
|
||||
Short: "Apply migrations",
|
||||
Long: `Apply all or a specific number of migrations.
|
||||
If steps is not provided, all pending migrations will be applied.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runMigration("up", steps)
|
||||
},
|
||||
}
|
||||
|
||||
var migrateDownCmd = &cobra.Command{
|
||||
Use: "down [steps]",
|
||||
Short: "Revert migrations",
|
||||
Long: `Revert all or a specific number of migrations.
|
||||
If steps is not provided, all applied migrations will be reverted.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runMigration("down", steps)
|
||||
},
|
||||
}
|
||||
|
||||
var migrateVersionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show current migration version",
|
||||
Long: `Display the current migration version of the database.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
showMigrationVersion()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
migrateCmd.AddCommand(migrateUpCmd)
|
||||
migrateCmd.AddCommand(migrateDownCmd)
|
||||
migrateCmd.AddCommand(migrateVersionCmd)
|
||||
|
||||
migrateCmd.PersistentFlags().StringVarP(&migrationsDir, "migrations", "m", "database/migrations", "Directory containing migration files")
|
||||
migrateCmd.PersistentFlags().IntVarP(&steps, "steps", "s", 0, "Number of migrations to apply or revert (0 means all)")
|
||||
}
|
||||
|
||||
func runMigration(direction string, steps int) {
|
||||
dbPath := viper.GetString("db")
|
||||
logger := log.New(os.Stdout, "MCIAS Migration: ", log.LstdFlags)
|
||||
|
||||
// Ensure migrations directory exists
|
||||
absPath, err := filepath.Abs(migrationsDir)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to get absolute path for migrations directory: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
logger.Fatalf("Migrations directory does not exist: %s", absPath)
|
||||
}
|
||||
|
||||
// Open database connection
|
||||
db, err := openDatabase(dbPath)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to open database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Create migration driver
|
||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create migration driver: %v", err)
|
||||
}
|
||||
|
||||
// Create migrate instance
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
fmt.Sprintf("file://%s", absPath),
|
||||
"sqlite3", driver)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create migration instance: %v", err)
|
||||
}
|
||||
|
||||
// Run migration
|
||||
if direction == "up" {
|
||||
if steps > 0 {
|
||||
err = m.Steps(steps)
|
||||
} else {
|
||||
err = m.Up()
|
||||
}
|
||||
if err != nil && err != migrate.ErrNoChange {
|
||||
logger.Fatalf("Failed to apply migrations: %v", err)
|
||||
}
|
||||
logger.Println("Migrations applied successfully")
|
||||
} else if direction == "down" {
|
||||
if steps > 0 {
|
||||
err = m.Steps(-steps)
|
||||
} else {
|
||||
err = m.Down()
|
||||
}
|
||||
if err != nil && err != migrate.ErrNoChange {
|
||||
logger.Fatalf("Failed to revert migrations: %v", err)
|
||||
}
|
||||
logger.Println("Migrations reverted successfully")
|
||||
}
|
||||
}
|
||||
|
||||
func showMigrationVersion() {
|
||||
dbPath := viper.GetString("db")
|
||||
logger := log.New(os.Stdout, "MCIAS Migration: ", log.LstdFlags)
|
||||
|
||||
// Open database connection
|
||||
db, err := openDatabase(dbPath)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to open database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Create migration driver
|
||||
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create migration driver: %v", err)
|
||||
}
|
||||
|
||||
// Create migrate instance
|
||||
absPath, err := filepath.Abs(migrationsDir)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to get absolute path for migrations directory: %v", err)
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
fmt.Sprintf("file://%s", absPath),
|
||||
"sqlite3", driver)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create migration instance: %v", err)
|
||||
}
|
||||
|
||||
// Get current version
|
||||
version, dirty, err := m.Version()
|
||||
if err != nil {
|
||||
if err == migrate.ErrNilVersion {
|
||||
logger.Println("No migrations have been applied yet")
|
||||
return
|
||||
}
|
||||
logger.Fatalf("Failed to get migration version: %v", err)
|
||||
}
|
||||
|
||||
logger.Printf("Current migration version: %d (dirty: %t)", version, dirty)
|
||||
}
|
||||
|
||||
func openDatabase(dbPath string) (*sql.DB, error) {
|
||||
// Ensure database directory exists
|
||||
dbDir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dbDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create database directory: %w", err)
|
||||
}
|
||||
|
||||
// Open database connection
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
Reference in New Issue
Block a user