From 048940ef7a9ea7536067edfc4e3998ee47498ec3 Mon Sep 17 00:00:00 2001 From: jms Date: Sat, 24 Oct 2020 23:12:10 -0500 Subject: [PATCH] good progress --- cmd/add.go | 32 ++--- cmd/load.go | 17 +-- cmd/root.go | 6 +- config/config.go | 29 +++-- data/data.go | 26 ++++- db/db.go | 55 +++++++++ docker-compose.yml | 18 +++ go.mod | 3 +- go.sum | 1 + loader/loader.go | 282 +++++++++++++++++++++++++++++++++++++++++++++ logger/logger.go | 5 +- main_test.go | 70 ++++++++++- sql/localDB.sql | 21 ++++ ui/ui.go | 24 ++++ 14 files changed, 535 insertions(+), 54 deletions(-) create mode 100644 db/db.go create mode 100644 docker-compose.yml create mode 100644 loader/loader.go create mode 100644 sql/localDB.sql create mode 100644 ui/ui.go diff --git a/cmd/add.go b/cmd/add.go index ea6fab7..5f90cd9 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -46,7 +46,6 @@ to quickly create a Cobra application.`, os.Exit(1) } - }, } @@ -64,7 +63,6 @@ func init() { // addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - const focusedTextColor = "205" var ( @@ -75,7 +73,6 @@ var ( blurredSubmitButton = "[ " + te.String("Submit").Foreground(color("240")).String() + " ]" ) - // Will ask a user for a unique name until they exit or provide one. func cfgRename(c data.HostDetails) { p := tea.NewProgram(renameModel(c)) @@ -109,15 +106,14 @@ func toFile(fp string, c data.HostDetails) { tea.Quit() } - type model struct { - index int - dbFileName input.Model - hostName input.Model - dbName input.Model - userName input.Model - secret input.Model - submitButton string + index int + dbFileName input.Model + hostName input.Model + dbName input.Model + userName input.Model + secret input.Model + submitButton string } func initialModel() model { @@ -143,13 +139,10 @@ func initialModel() model { secret.Placeholder = "secret" secret.Prompt = blurredPrompt - - return model{0, dbFileName, hostName, dbName, userName, secret, blurredSubmitButton} } - func (m model) Init() tea.Cmd { return tea.Batch( input.Blink(m.dbFileName), @@ -160,7 +153,6 @@ func (m model) Init() tea.Cmd { ) } - func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd @@ -224,7 +216,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.userName = inputs[3] m.secret = inputs[4] - if m.index == len(inputs) { m.submitButton = focusedSubmitButton } else { @@ -240,7 +231,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } - func updateInputs(msg tea.Msg, m model) (model, tea.Cmd) { var ( cmd tea.Cmd @@ -261,11 +251,9 @@ func updateInputs(msg tea.Msg, m model) (model, tea.Cmd) { m.secret, cmd = input.Update(msg, m.secret) - return m, tea.Batch(cmds...) } - func (m model) View() string { s := "\n" @@ -294,7 +282,7 @@ func configurator(t []input.Model) { var cfgFileName string var fp string var homeDir string - c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()} + c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()} cfgFileName = t[0].Value() homeDir, err := os.UserHomeDir() if err != nil { @@ -311,7 +299,6 @@ func configurator(t []input.Model) { } toFile(fp, c) - } func renameModel(c data.HostDetails) rename { @@ -363,7 +350,6 @@ func (m rename) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } - m.textInput, cmd = input.Update(msg, m.textInput) return m, cmd } @@ -396,4 +382,4 @@ func Frame() tea.Cmd { return tea.Tick(time.Second/60, func(time.Time) tea.Msg { return FrameMsg{} }) -} \ No newline at end of file +} diff --git a/cmd/load.go b/cmd/load.go index 075c61f..caae593 100644 --- a/cmd/load.go +++ b/cmd/load.go @@ -17,9 +17,10 @@ package cmd import ( "github.com/spf13/cobra" - "pgee/box" - "pgee/utils" "pgm/data" + "pgm/loader" + "pgm/logger" + "os" ) // billyCmd represents the billy command @@ -35,15 +36,18 @@ to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { var dbcfg string var title string - var hosts []string - hosts = data.ReadHosts() + var pgHosts []string + pgHosts = data.ReadHosts() title = "choose db to load, \"l\" or enter will load your choice" - box.Box(pgee, title, dbcfg) + err := loader.Loader(pgHosts, title, dbcfg) + if err != nil { + logger.Logger("[ERROR] loading error for testing, ignore.") + } + os.Exit(0) }, } - func init() { rootCmd.AddCommand(loadCmd) @@ -57,4 +61,3 @@ func init() { // is called directly, e.g.: // billyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - diff --git a/cmd/root.go b/cmd/root.go index 04ac657..5f913f0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,6 +21,7 @@ import ( "log" "os" "pgm/config" + "pgm/logger" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" @@ -64,6 +65,7 @@ func init() { // Cobra also supports local flags, which will only run // when this action is called directly. rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + viper.BindPFlag("temp_db", rootCmd.PersistentFlags().Lookup("temp_db")) } // initConfig reads in config file and ENV variables if set. @@ -78,7 +80,6 @@ func InitConfig() { fmt.Println(err) os.Exit(1) } - os.Setenv("PGMHOME", home) // Search config in home directory with name ".pgm" (without extension). viper.AddConfigPath(home) viper.SetConfigName(".pgm") @@ -100,7 +101,6 @@ func InitConfig() { // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + logger.Logger("[LOG] Using config file:"+ viper.ConfigFileUsed()) } } - diff --git a/config/config.go b/config/config.go index 70e8e29..3f094cf 100644 --- a/config/config.go +++ b/config/config.go @@ -22,19 +22,19 @@ func IsInit(h string, s string, l string, cfgFile string) bool { } // if there is no $HOME/.pgm dir if _, err := os.Stat(homeDirectory + "/.pgm"); os.IsNotExist(err) { - err = os.Mkdir(homeDirectory + "/.pgm/", 0755) + err = os.Mkdir(homeDirectory+"/.pgm/", 0755) if err != nil { log.Fatal(err) } } // creates logs, src, and hosts directories. for _, f := range dirs { - if len(f) >0 { - err := os.MkdirAll(homeDirectory + "/" + f, 0755) - if err != nil { - log.Fatal(err) - } - isCfg = append(isCfg, true) + if len(f) > 0 { + err := os.MkdirAll(homeDirectory+"/"+f, 0755) + if err != nil { + log.Fatal(err) + } + isCfg = append(isCfg, true) } } @@ -44,23 +44,20 @@ func IsInit(h string, s string, l string, cfgFile string) bool { } } return true - } - +} // determines if key exists in config file -func cfgExists( s string, d []string, cfgFile string) []string { +func cfgExists(s string, d []string, cfgFile string) []string { var dir []string var cfgPath string dir = strings.Split(s, "/") cfgPath = dir[len(dir)-1] - - configDirectoryKey := parseCfg(cfgPath) if err := viper.ReadInConfig(); err == nil { viper.SetConfigFile(cfgFile) } - if viper.GetString(configDirectoryKey)!=""{ + if viper.GetString(configDirectoryKey) != "" { a := viper.GetString(configDirectoryKey) d = append(d, a) } else { @@ -70,12 +67,12 @@ func cfgExists( s string, d []string, cfgFile string) []string { } // neatly return a string formatted to match the config file -func parseCfg (s string) string { +func parseCfg(s string) string { var directory string var isTest string // if we have test directories these are configured already in .pgm.yaml isTest = s[:3] - if isTest == "test"{ + if isTest == "test" { return s } directory = "%s_dir" @@ -105,4 +102,4 @@ func ReadConfig() (*viper.Viper, error) { return v, err } return v, nil -} \ No newline at end of file +} diff --git a/data/data.go b/data/data.go index 4b9e1e9..e0b3a25 100644 --- a/data/data.go +++ b/data/data.go @@ -2,6 +2,7 @@ package data import ( "fmt" + "github.com/spf13/viper" "log" "os" "pgm/logger" @@ -23,7 +24,7 @@ func ReadHosts() []string { log.Fatal(err) } - file, err := os.Open(homeDir +"/.pgm/hosts") + file, err := os.Open(homeDir + "/.pgm/hosts") if err != nil { logged := logger.Logger("failed opening directory: %s" + err.Error()) if logged == true { @@ -40,3 +41,26 @@ func ReadHosts() []string { return pgee } +// returns a value from the .pgm.yaml file +func ViperReturnKey(k string ) string { + var h string + var s string + h, err := os.UserHomeDir() + if err != nil { + logger.Logger("[ERROR] could not set home directory: " + err.Error()) + os.Exit(0) + } + viper.SetConfigName("scr") + viper.SetConfigType("json") + + viper.AddConfigPath(h + "/.pgm/scn/") + err = viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + logger.Logger("[ERROR] failed to load viper config: " + err.Error()) + fmt.Println("error occurred and was logged.") + os.Exit(1) + } + s = viper.GetString(k) + logger.Logger("[LOG] got value from .pgm.yaml: " + s ) + return s +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..38a7e7e --- /dev/null +++ b/db/db.go @@ -0,0 +1,55 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + _ "github.com/lib/pq" + "os" + "pgm/data" + "pgm/logger" +) + +func DbConn(c data.HostDetails) *sql.DB { + var h string + var u string + var pw string + var dbn string + var psqlInfo string + h = c.Hostname + u = c.Username + pw = c.Secret + dbn = c.DatabaseName + psqlInfo = fmt.Sprintf("host=%s port=%d user=%s "+ + "password=%s dbname=%s sslmode=disable", + h, 5432, u, pw, dbn) + db, err := sql.Open("postgres", psqlInfo) + testUserName := data.ViperReturnKey("test_user") + logger.Logger("[LOG] reserved username is " + testUserName) + if err != nil { + switch u { + case testUserName: + logger.Logger("[ERROR] go tests expects to connect with the docker compose database." + + "[ERROR] run \"docker-compose up\" and re-run tests") + os.Exit(1) + default: + logger.Logger("[ERROR] db error: " + err.Error()) + } + + } + + ctx := context.Background() + err = db.PingContext(ctx) + if err != nil { + switch u { + case testUserName: + logger.Logger("[ERROR] db error, unable to ping: " + err.Error()+ + "[ERROR] ensure docker database is running and re-run tests") + os.Exit(1) + default: + logger.Logger("[ERROR] db error: " + err.Error()) + } + } + + return db +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4ed3a72 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.7' + +services: + pg-db: + image: postgres:11.5 + container_name: pg-db + expose: + - "5432" + ports: + - "5432:5432" + environment: + - POSTGRES_DB=localdb + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + volumes: + - ${PWD}/sql/localDB.sql:/docker-entrypoint-initdb.d/localdb.sql + + diff --git a/go.mod b/go.mod index 05a7a08..4786c65 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,10 @@ require ( github.com/charmbracelet/bubbles v0.7.1 github.com/charmbracelet/bubbletea v0.12.1 github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/lib/pq v1.8.0 github.com/magiconair/properties v1.8.4 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/mitchellh/mapstructure v1.3.3 github.com/muesli/termenv v0.7.4 github.com/pelletier/go-toml v1.8.1 // indirect github.com/spf13/afero v1.4.1 // indirect diff --git a/go.sum b/go.sum index 2e61637..d4ccb4e 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/loader/loader.go b/loader/loader.go new file mode 100644 index 0000000..da9b361 --- /dev/null +++ b/loader/loader.go @@ -0,0 +1,282 @@ +package loader + +import ( + "database/sql" + "encoding/json" + "fmt" + tea "github.com/charmbracelet/bubbletea" + "github.com/mitchellh/mapstructure" + "github.com/muesli/termenv" + "github.com/spf13/viper" + "io/ioutil" + "os" + "pgm/config" + "pgm/data" + "pgm/db" + "pgm/logger" + "pgm/ui" + "strconv" + "strings" + "time" +) + +type host struct { + tic int + t string + choices []string + choice int + chosen bool + Quitting bool + Frames int + File string + isTest bool +} + +var ( + term = termenv.ColorProfile() + subtle = makeFgStyle("241") + dot = colorFg(" • ", "236") +) + +func Loader(t []string, s string, b string) error { + var hostChoice host + var isT bool + isT = setTest(s) + logger.Logger("[LOG] is loader test: " + strconv.FormatBool(hostChoice.isTest)) + hostChoice = host{60, s, t, 0, false, false, 0, b, isT} + if err := tea.NewProgram(hostChoice).Start(); err != nil { + if isT == true { + return nil + } + logger.Logger("[ERROR] tea error occured") + os.Exit(1) + } + return nil + +} + +// method to enable go tests if set to true +// loader function will test that it can load the ui to chose a database +// and quits the ui +func setTest(s string) bool { + logger.Logger("[LOG] is test: " + s) + if s == "test" { + return true + } + return false +} + +func (m host) Init() tea.Cmd { + return tick() +} + +func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + case tea.KeyMsg: + switch msg.String() { + case "q", "esc": + m.Quitting = true + return m, tea.Quit + case "j", "down": + m.choice += 1 + if m.choice > len(m.choices) { + m.choice = len(m.choices) + } + case "k", "up": + m.choice -= 1 + if m.choice < 0 { + m.choice = 0 + } + case "l", "enter": + m.File = m.choices[m.choice] + m.chosen = true + return m, Frame() + } + + case tickMsg: + if m.isTest == true { + m.File = "test" + m.Quitting = true + return m, tea.Quit + } + if m.tic == 0 { + m.Quitting = true + return m, tea.Quit + } + m.tic -= 1 + return m, tick() + } + return m, nil +} + +// Update loop for the second view after a choice has been made +func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if msg, ok := msg.(tea.KeyMsg); ok { + k := msg.String() + if k == "q" || k == "esc" || k == "ctrl+c" { + m.Quitting = true + return m, tea.Quit + } + } + + if !m.chosen { + return choicesUpdate(msg, m) + } + return m, tea.Quit +} + +// Views return a string based on data in the host. That string which will be +// rendered to the terminal. +func (m host) View() string { + logger.Logger("[LOG] view initialized") + var s string + if m.Quitting { + logger.Logger("[LOG] leaving pgm") + return "leaving pgm..." + + } + if !m.chosen { + s = choicesView(m) + } else { + tea.Quit() + LoadUi(m) + } + return s +} + +// Passes our config to the screen file. +// screen is tasked with rendering more in depth views. +func LoadUi(m host) { + var g data.HostDetails + var h string + h, err := os.UserHomeDir() + if err != nil { + logger.Logger("[ERROR] uable to set home dir: " + err.Error()) + os.Exit(0) + } + config.ReadConfig() + l := viper.GetString("hosts_dir") + filepath := h + "/" + l + "/" + m.File + loadhost(filepath, &g) + Screener(g) + +} + +// Creates the menu for choosing a file from your configs. +// Exits after 60 seconds. +// Gets Title from outside in the load method if this... should ever be needed elsewhere. +func choicesView(m host) string { + var menu string + c := m.choice + + tpl := m.t + "\n\n" + tpl += "%s\n\n" + tpl += "menu exits in %s seconds\n\n" + tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit") + for i, s := range m.choices { + menu = menu + "::" + checkbox(s, c == i) + } + mn := strings.Replace(menu, "::", "\n", -1) + choices := fmt.Sprintf( + mn, + ) + + return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79")) +} + +// the rest of these functions are to do with looks and aesthetics + +// returns a neat checkbox +func checkbox(label string, checked bool) string { + if checked { + return colorFg("[x] "+label, "212") + } + return fmt.Sprintf("[ ] %s", label) +} + +// fun colors +func colorFg(val, color string) string { + return termenv.String(val).Foreground(term.Color(color)).String() +} + +// Return a function that will colorize the foreground of a given string. +func makeFgStyle(color string) func(string) string { + return termenv.Style{}.Foreground(term.Color(color)).Styled +} + +// Frame event +func Frame() tea.Cmd { + return tea.Tick(time.Second/60, func(time.Time) tea.Msg { + return FrameMsg{} + }) +} + +type tickMsg struct{} +type FrameMsg struct{} + +// exits if no choice is selected in a minute +func tick() tea.Cmd { + return tea.Tick(time.Second, func(time.Time) tea.Msg { + return tickMsg{} + }) +} + +// loads a yaml file into the correct format for consumption. +// errs the pipeline step if there is not a configuration in this repository. +func loadhost(c string, p *data.HostDetails) *data.HostDetails { + logger.Logger("[LOG] file path string is: " + c) + cfg, err := ioutil.ReadFile(c) + if err != nil { + logger.Logger("[ERROR] unable to find this filepath: " + err.Error()) + os.Exit(1) + } + json.Unmarshal(cfg, &p) + return p +} + +func Screener(g data.HostDetails) error { + var scn Scr + var dbConn *sql.DB + logger.Logger("[LOG] starting screen") + s := data.ViperReturnKey("UserSessions") + + mapstructure.Decode(s, &scn) + dbConn = db.DbConn(g) + switch g.Username { + case "test": + dbConn.Close() + return nil + } + ui.GraphicUi(dbConn) + return nil +} + +// Simple struct for a view in the UI. +type Scr struct { + Query string `json:"query"` + Columns int `json:"columns"` + UIType string `json:"uiType"` + Title string `json:"title"` + Quitting bool +} + +func (m Scr) Init() tea.Cmd { + return tick() +} + +func (m Scr) View() string { + + return m.Query +} + +func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if msg, ok := msg.(tea.KeyMsg); ok { + k := msg.String() + if k == "q" || k == "esc" || k == "ctrl+c" { + m.Quitting = true + return m, tea.Quit + } + } + return m, nil +} diff --git a/logger/logger.go b/logger/logger.go index 6581a9b..e1734fc 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -7,7 +7,10 @@ import ( func Logger(s string) bool { var h string - h = os.Getenv("PGMHOME") + h, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } if s == "test" { os.Create("/var/tmp/PGMLOGTEST") file, err := os.OpenFile("/var/tmp/PGMLOGTEST", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) diff --git a/main_test.go b/main_test.go index 63fc837..32e6d06 100644 --- a/main_test.go +++ b/main_test.go @@ -1,10 +1,13 @@ package main_test import ( + "encoding/json" "fmt" "github.com/spf13/viper" "os" "pgm/config" + "pgm/data" + "pgm/loader" "pgm/logger" "testing" ) @@ -14,7 +17,7 @@ import ( func TestInit(t *testing.T) { t.Parallel() var cfg string - testCases := []initTests{ + testCases := []initTest{ {a: "junk", b: "notreal", c: "failingonpurpose", want: true}, {a: "test_host_dir", b: "test_screen_dr", c: "test_log_dir", want: true}, } @@ -57,7 +60,70 @@ func TestLoadConfig(t *testing.T) { } -type initTests struct { +// loader function will test that it can load the ui to chose a database +// and quits the ui. +// "test" is added into the array of files a user has configured and selected +func TestLoader(t *testing.T) { + t.Parallel() + var tc []string + var s string + var b string + var want error = nil + s = "test" + b = "test" + tc = data.ReadHosts() + tc = append(tc, "test") + got := loader.Loader(tc, s, b) + if want != got { + t.Errorf("want %t, got %t", want, got) + } + +} + + +func TestScreener(t *testing.T){ + t.Parallel() + var p data.HostDetails + var testDb string + var want error = nil + var h string + h, err := os.UserHomeDir() + if err != nil { + logger.Logger("[ERROR] could not set home directory: " + err.Error()) + os.Exit(0) + } + viper.SetConfigName(".pgm") // config file name without extension + viper.SetConfigType("yaml") + viper.AddConfigPath(h) + viper.AutomaticEnv() // read value ENV variable + + err = viper.ReadInConfig() + if err != nil { + logger.Logger("[ERROR] fatal error config file: " + err.Error()) + os.Exit(1) + } + testDb = viper.GetString("test_db") + err = json.Unmarshal([]byte(testDb), &p) + if err != nil { + logger.Logger("[LOG] test screener failed: " + err.Error()) + os.Exit(1) + } + got := loader.Screener(p) + if want != got { + t.Errorf("want %t, got %t", want, got) + } + + + +} + +type viperHostTest struct { + a string + want bool +} + + +type initTest struct { a, b, c string want bool } \ No newline at end of file diff --git a/sql/localDB.sql b/sql/localDB.sql new file mode 100644 index 0000000..a073583 --- /dev/null +++ b/sql/localDB.sql @@ -0,0 +1,21 @@ +create schema test; +create table test.employees ( + Id SERIAL NOT NULL PRIMARY KEY, + Name VARCHAR(50), + Location VARCHAR(50) +); +create table test.company ( + cid SERIAL NOT NULL PRIMARY KEY, + Name VARCHAR(50) +); +create table test.employer ( + eid SERIAL NOT NULL PRIMARY KEY, + Id INT, + cid INT +); +ALTER TABLE test.employer add constraint emp_fk foreign key(Id) REFERENCES test.employees(id) ON UPDATE CASCADE ON DELETE CASCADE; +insert into test.employees (name,location) VALUES ('bob','kalamazoo'); +create role test with login password 'test'; +insert into test.employees (name,location) VALUES ('cheeks','magoo'); +update test.employees set location = 'buttsville' where name = 'cheeks'; +grant all privileges on schema test to test; diff --git a/ui/ui.go b/ui/ui.go new file mode 100644 index 0000000..d24345e --- /dev/null +++ b/ui/ui.go @@ -0,0 +1,24 @@ +package ui + +import ( + tea "github.com/charmbracelet/bubbletea" + "database/sql" + "pgm/logger" + "fmt" + "os" +) + +func GraphicUi(db *sql.DB, ) { + row, err := db.Query("Select name from test.employees") + if err != nil { + logger.Logger("[ERROR] database error occured: " + err.Error()) + os.Exit(0) + } + defer row.Close() + for row.Next() { + fmt.Println(row) + } + tea.Quit() + +} +