From 487060387b847c176be0181f2bf098cdb6ff7705 Mon Sep 17 00:00:00 2001
From: "Dominik.Sigmund" <dominik.sigmund@br.de>
Date: Fri, 12 Jul 2024 14:55:04 +0200
Subject: [PATCH] Added Serve

---
 commands/serve.go | 211 ++++++++++++++++++++++++++++++++++++++++++++++
 config/config.go  |  23 +++++
 go.mod            |   8 +-
 go.sum            |   4 +
 main.go           |   6 +-
 5 files changed, 248 insertions(+), 4 deletions(-)
 create mode 100644 commands/serve.go

diff --git a/commands/serve.go b/commands/serve.go
new file mode 100644
index 0000000..8670375
--- /dev/null
+++ b/commands/serve.go
@@ -0,0 +1,211 @@
+package commands
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"datasmith/config"
+
+	"github.com/fsnotify/fsnotify"
+	"gopkg.in/yaml.v2"
+)
+
+
+
+// Load the datasmith.yaml file
+func loadConfig(projectDir string) (config.DatasmithConfig, error) {
+	configFilePath := filepath.Join(projectDir, "datasmith.yaml")
+	file, err := os.Open(configFilePath)
+	if err != nil {
+		return config.DatasmithConfig{}, fmt.Errorf("error opening config file: %v", err)
+	}
+	defer file.Close()
+
+	var cfg config.DatasmithConfig
+	decoder := yaml.NewDecoder(file)
+	if err := decoder.Decode(&cfg); err != nil {
+		return config.DatasmithConfig{}, fmt.Errorf("error decoding config file: %v", err)
+	}
+
+	return cfg, nil
+}
+
+func Serve() {
+	projectDir := "." // Assuming current directory is the project directory
+	config, err := loadConfig(projectDir)
+	if err != nil {
+		fmt.Printf("Error loading config: %v\n", err)
+		return
+	}
+	fmt.Println("Config loaded successfully.")
+
+	// Determine which container tool to use (Docker or Podman)
+	containerTool := "docker"
+	if _, err := exec.LookPath("podman"); err == nil {
+		containerTool = "podman"
+	} else if _, err := exec.LookPath("docker"); err != nil {
+		fmt.Println("Error: neither Docker nor Podman is installed.")
+		return
+	}
+
+	// Build the Docker image
+	fmt.Println("Building the Docker image...")
+	if err := buildDockerImage(containerTool, config); err != nil {
+		fmt.Printf("Error building Docker image: %v\n", err)
+		return
+	}
+	fmt.Println("Docker image built successfully.")
+
+	// Start the database container
+	fmt.Println("Starting the database container...")
+	if err := startDatabaseContainer(containerTool, config); err != nil {
+		fmt.Printf("Error starting database container: %v\n", err)
+		return
+	}
+	fmt.Println("Database container started successfully.")
+
+	// Print connection information
+	printConnectionInfo(config)
+
+	fmt.Println("Watching for changes in the SQL files...")
+
+	// Watch for changes in the SQL files
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		fmt.Printf("Error creating file watcher: %v\n", err)
+		return
+	}
+	defer watcher.Close()
+
+	done := make(chan bool)
+
+	go func() {
+		for {
+			select {
+			case event, ok := <-watcher.Events:
+				if !ok {
+					return
+				}
+				fmt.Println("Event:", event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					fmt.Println("Modified file:", event.Name)
+					if err := rebuildAndRestartContainer(containerTool, config); err != nil {
+						fmt.Printf("Error rebuilding and restarting container: %v\n", err)
+					} else {
+						fmt.Println("Container rebuilt and restarted successfully.")
+					}
+				}
+			case err, ok := <-watcher.Errors:
+				if !ok {
+					return
+				}
+				fmt.Println("Error:", err)
+			}
+		}
+	}()
+
+	err = watcher.Add("sql")
+	if err != nil {
+		fmt.Printf("Error adding watcher: %v\n", err)
+		return
+	}
+
+	<-done
+}
+
+func buildDockerImage(containerTool string, config config.DatasmithConfig) error {
+	imageName := fmt.Sprintf("ds_%s:%s", config.Name, config.Version)
+	fmt.Printf("Building Docker image: %s\n", imageName)
+
+	var buildCmd *exec.Cmd
+	switch config.DbType {
+	case "mysql":
+		buildCmd = exec.Command(containerTool, "build",
+			"-t", imageName,
+			"--build-arg", "DB_USER=user",
+			"--build-arg", "DB_PASSWORD=password",
+			"--build-arg", fmt.Sprintf("DB_DATABASE=%s", config.Name),
+			"--build-arg", "MARIADB_ROOT_PASSWORD=example",
+			".")
+	case "postgres":
+		buildCmd = exec.Command(containerTool, "build",
+			"-t", imageName,
+			"--build-arg", "DB_USER=user",
+			"--build-arg", "DB_PASSWORD=password",
+			"--build-arg", fmt.Sprintf("DB_DATABASE=%s", config.Name),
+			"--build-arg", "POSTGRES_PASSWORD=example",
+			".")
+	default:
+		return fmt.Errorf("unsupported database type: %s", config.DbType)
+	}
+
+	buildCmd.Stdout = os.Stdout
+	buildCmd.Stderr = os.Stderr
+	return buildCmd.Run()
+}
+
+func startDatabaseContainer(containerTool string, config config.DatasmithConfig) error {
+	var runCmd *exec.Cmd
+	switch config.DbType {
+	case "mysql":
+		runCmd = exec.Command(containerTool, "run", "--rm", "-d",
+			"-e", "MYSQL_ROOT_PASSWORD=example",
+			"-e", fmt.Sprintf("DB_DATABASE=%s", config.Name),
+			"-e", "DB_USER=user",
+			"-e", "DB_PASSWORD=password",
+			"-p", "3306:3306",
+			"--name", "datasmith_db_container",
+			"datasmith_db_image")
+	case "postgres":
+		runCmd = exec.Command(containerTool, "run", "--rm", "-d",
+			"-e", "POSTGRES_PASSWORD=example",
+			"-e", fmt.Sprintf("POSTGRES_DB=%s", config.Name),
+			"-e", "POSTGRES_USER=user",
+			"-p", "5432:5432",
+			"--name", "datasmith_db_container",
+			"datasmith_db_image")
+	default:
+		return fmt.Errorf("unsupported database type: %s", config.DbType)
+	}
+
+	runCmd.Stdout = os.Stdout
+	runCmd.Stderr = os.Stderr
+	return runCmd.Run()
+}
+
+func rebuildAndRestartContainer(containerTool string, config config.DatasmithConfig) error {
+	fmt.Println("Stopping the current database container...")
+	stopCmd := exec.Command(containerTool, "stop", "datasmith_db_container")
+	stopCmd.Stdout = os.Stdout
+	stopCmd.Stderr = os.Stderr
+	if err := stopCmd.Run(); err != nil {
+		return fmt.Errorf("error stopping database container: %v", err)
+	}
+
+	fmt.Println("Rebuilding the Docker image...")
+	if err := buildDockerImage(containerTool, config); err != nil {
+		return fmt.Errorf("error rebuilding Docker image: %v", err)
+	}
+
+	fmt.Println("Restarting the database container...")
+	if err := startDatabaseContainer(containerTool, config); err != nil {
+		return fmt.Errorf("error restarting database container: %v", err)
+	}
+
+	return nil
+}
+
+func printConnectionInfo(config config.DatasmithConfig) {
+	fmt.Println("Connection Information:")
+	switch config.DbType {
+	case "mysql":
+		fmt.Println("MySQL connection string:")
+		fmt.Printf("Host: localhost\nPort: 3306\nDatabase: %s\nUser: user\nPassword: password\n", config.Name)
+	case "postgres":
+		fmt.Println("PostgreSQL connection string:")
+		fmt.Printf("Host: localhost\nPort: 5432\nDatabase: %s\nUser: user\nPassword: password\n", config.Name)
+	default:
+		fmt.Printf("Unsupported database type: %s\n", config.DbType)
+	}
+}
\ No newline at end of file
diff --git a/config/config.go b/config/config.go
index bb2e6a4..0e618f7 100644
--- a/config/config.go
+++ b/config/config.go
@@ -7,6 +7,29 @@ import (
 	"os/user"
 	"path/filepath"
 )
+type Field struct {
+	Name         string      `json:"name"`
+	Type         string      `json:"type"`
+	PrimaryKey   bool        `json:"primary_key,omitempty"`
+	ForeignKey   string      `json:"foreign_key,omitempty"`
+	Unique       bool        `json:"unique,omitempty"`
+	NotNull      bool        `json:"not_null,omitempty"`
+	AutoIncrement bool       `json:"auto_increment,omitempty"`
+	DefaultValue interface{} `json:"default_value,omitempty"`
+}
+
+type TableModel struct {
+	Fields []Field `json:"fields"`
+}
+
+type DatasmithConfig struct {
+	Name string 									`yaml:"name"`
+	Version string 								`yaml:"version"`
+	CreatedAt string 						`yaml:"created_at"`
+	DbType string              		`yaml:"database_type"`
+	Tables map[string]TableModel 	`yaml:"tables"`
+}
+
 
 type Configuration struct {
 	User string `json:"author"`
diff --git a/go.mod b/go.mod
index cbc7f69..6aef5d3 100644
--- a/go.mod
+++ b/go.mod
@@ -2,9 +2,13 @@ module datasmith
 
 go 1.22.4
 
+require (
+	github.com/fsnotify/fsnotify v1.7.0
+	gopkg.in/yaml.v2 v2.4.0
+)
+
 require (
 	github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
 	github.com/manifoldco/promptui v0.9.0 // indirect
-	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
-	gopkg.in/yaml.v2 v2.4.0
+	golang.org/x/sys v0.4.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 3dd0071..32d3e00 100644
--- a/go.sum
+++ b/go.sum
@@ -2,11 +2,15 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
 github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
 golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/main.go b/main.go
index 8ed57fb..876e7dd 100644
--- a/main.go
+++ b/main.go
@@ -8,7 +8,7 @@ import (
 	"datasmith/config"
 )
 
-var version = "0.0.1"
+var version = "1.0.0"
 
 func main() {
 	config.LoadConfig()
@@ -56,11 +56,13 @@ func main() {
 			return
 		}
 		commands.Add(tableName, model)
+	case "serve":
+		commands.Serve()
 	case "version":
 		commands.Version(version)
 	case "help":
 		commands.Help()
-	// TODO: list tables, show table, remove table, generate test data
+	// TODO:  generate test data
 	default:
 		fmt.Printf("Unknown command: %s\n", command)
 		commands.Help()
-- 
GitLab