diff --git a/cmd/dump.go b/cmd/dump.go index 539c09de..5c90b900 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -2,6 +2,7 @@ package cmd import ( "archive/zip" + "errors" "fmt" "io/ioutil" "os" @@ -49,6 +50,12 @@ var dumpCmd = &cobra.Command{ os.Exit(1) } + _, err = os.Stat(output) + if !errors.Is(err, os.ErrNotExist) { + fmt.Println(color.RedString("%s exists", output)) + os.Exit(1) + } + // Load model err = engine.Load(config.Conf) if err != nil { @@ -57,10 +64,8 @@ var dumpCmd = &cobra.Command{ } if dumpModel != "" { - mod, has := gou.Models[dumpModel] - if has { - mod.Migrate(true) - } + fmt.Println(color.YellowString(L("Not supported yet"))) + os.Exit(1) return } @@ -100,9 +105,9 @@ var dumpCmd = &cobra.Command{ }, } -func init() { - dumpCmd.PersistentFlags().StringVarP(&dumpModel, "name", "n", "", L("Model name")) -} +// func init() { +// // dumpCmd.PersistentFlags().StringVarP(&dumpModel, "name", "n", "", L("Model name")) +// } // gzipfiles func zipfiles(files []string, output string, process func(file string)) error { diff --git a/cmd/restore.go b/cmd/restore.go new file mode 100644 index 00000000..248e4fc9 --- /dev/null +++ b/cmd/restore.go @@ -0,0 +1,206 @@ +package cmd + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var restoreForce bool = false +var restoreCmd = &cobra.Command{ + Use: "restore", + Short: L("Restore the application data"), + Long: L("Restore the application data"), + Run: func(cmd *cobra.Command, args []string) { + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + os.Exit(1) + } + + zipfile, err := filepath.Abs(args[0]) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + Boot() + + if !restoreForce && config.Conf.Mode == "production" { + fmt.Println(color.WhiteString(L("TRY:")), color.GreenString("%s restore --force", share.BUILDNAME)) + exception.New(L("Retore is not allowed on production mode."), 403).Throw() + } + + // Unzip files + dst := unzipFile(zipfile, func(file string) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Unzip the file: %s"), file)) + }) + + // 加载数据模型 + err = engine.Load(config.Conf) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Restore models + restoreModels(filepath.Join(dst, "model")) + + // Restore Data + restoreData(filepath.Join(dst, "data")) + + // Clean + os.RemoveAll(dst) + + fmt.Println(color.GreenString(L("✨DONE✨"))) + }, +} + +func init() { + restoreCmd.PersistentFlags().BoolVarP(&restoreForce, "force", "", false, L("Force restore")) +} + +func restoreData(basePath string) { + + _, err := os.Stat(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Clean Data + dataPath := filepath.Join(config.Conf.Root, "data") + _, err = os.Stat(dataPath) + if err == nil { + os.RemoveAll(dataPath) + } + + err = os.Rename(basePath, dataPath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } +} + +func restoreModels(basePath string) { + + files, err := ioutil.ReadDir(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Migrate models + for _, mod := range gou.Models { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf(color.GreenString(L("\rUpdate schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) + err := mod.ForceCreateSchema() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } + + fmt.Println("") + + for _, file := range files { + namer := strings.Split(file.Name(), ".") + name := strings.Join(namer[:len(namer)-2], ".") + if mod, has := gou.Models[name]; has { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf(color.GreenString(L("\rRestore model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) + err := mod.Import(filepath.Join(basePath, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } + } +} + +func unzipFile(file string, process func(file string)) string { + _, err := os.Stat(file) + + if errors.Is(err, os.ErrNotExist) { + fmt.Println(color.RedString("%s not exists", file)) + os.Exit(1) + } + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dst := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s", filepath.Base(file), time.Now().Format("20060102150405"))) + os.MkdirAll(dst, 0755) + + archive, err := zip.OpenReader(file) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + defer archive.Close() + + for _, f := range archive.File { + filePath := filepath.Join(dst, f.Name) + process(f.Name) + + if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) { + fmt.Println(color.RedString(L("Fatal: invalid file path"))) + os.Exit(1) + } + if f.FileInfo().IsDir() { + os.MkdirAll(filePath, os.ModePerm) + continue + } + + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + fileInArchive, err := f.Open() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dstFile.Close() + fileInArchive.Close() + } + + return dst +} diff --git a/cmd/root.go b/cmd/root.go index 0da3979d..5760724a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,6 +93,7 @@ func init() { initCmd, serviceCmd, dumpCmd, + restoreCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory"))