From c15c16be8de2382bd8d230023c056a77b857b473 Mon Sep 17 00:00:00 2001 From: jake Date: Wed, 18 Jun 2025 18:38:01 -0400 Subject: [PATCH] finishing sqlite changes - finished default/active server logic - dev done - needs testing --- README.md | 7 +-- client/mcr.go | 15 ++++-- cmd/command/add.go | 3 +- cmd/command/command.go | 8 +-- cmd/command/delete.go | 3 +- cmd/command/run.go | 75 +++++++++++++++++++++++++++++ cmd/command/view.go | 2 +- cmd/destroy.go | 30 ++++++++---- cmd/init.go | 11 ++--- cmd/login.go | 2 +- cmd/root.go | 3 +- cmd/server/active.go | 35 ++++++++++++++ cmd/server/add.go | 2 +- cmd/server/delete.go | 3 +- cmd/server/server.go | 8 +-- cmd/server/update.go | 3 +- cmd/server/view.go | 5 +- database/sqlx.go | 107 +++++++++++++++++++++++++++++++++++------ model/data.go | 1 + 19 files changed, 254 insertions(+), 69 deletions(-) create mode 100644 cmd/command/run.go create mode 100644 cmd/server/active.go diff --git a/README.md b/README.md index c24324b..95573cf 100644 --- a/README.md +++ b/README.md @@ -117,10 +117,5 @@ mctl utilizes [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck this repo is currently in development and may encounter breaking changes, use a tag to prevent any surprises # TODO -6. Do we use CheckErr or use RunE 7. update readme, the commands are all broken -9. do saved commands run on default server only? Yes, but add -s - mctl command run tp nutlift -s server -10. RUN COMMAND -11. Command descriptions, examples, and silence usage -12. lol "default" server functionality (which means setting which one w default command) \ No newline at end of file +8. Maybe move off of fmt \ No newline at end of file diff --git a/client/mcr.go b/client/mcr.go index ea9452b..afc93df 100644 --- a/client/mcr.go +++ b/client/mcr.go @@ -5,6 +5,7 @@ import ( "fmt" "code.jakeyoungdev.com/jake/mctl/database" + "code.jakeyoungdev.com/jake/mctl/model" "github.com/jake-young-dev/mcr" ) @@ -29,9 +30,17 @@ func New(name string) (*Client, error) { } defer db.Close() - srv, err := db.GetServer(name) - if err != nil { - return nil, err + var srv model.Server + if name != "" { + srv, err = db.GetServer(name) + if err != nil { + return nil, err + } + } else { + srv, err = db.GetActiveServer() + if err != nil { + return nil, err + } } fmt.Printf("Logging into %s on port %d\n", srv.Server, srv.Port) diff --git a/cmd/command/add.go b/cmd/command/add.go index f54080e..63975ad 100644 --- a/cmd/command/add.go +++ b/cmd/command/add.go @@ -15,8 +15,7 @@ import ( var addCmd = &cobra.Command{ Use: "add", Example: "mctl command add", - // Short: "Saves a new server configuration", - // Long: `Saves server address, alias, port, and password.`, + Short: "Saves a new command to the database", Run: func(cmd *cobra.Command, args []string) { scanner := bufio.NewScanner(os.Stdin) diff --git a/cmd/command/command.go b/cmd/command/command.go index a295960..600466b 100644 --- a/cmd/command/command.go +++ b/cmd/command/command.go @@ -11,12 +11,6 @@ import ( var CommandCmd = &cobra.Command{ Use: "command", Example: "mctl command ", - // Short: "A remote console client", - // Long: `mctl is a terminal-friendly remote console client made to manage game servers.`, - // Version: "v0.3.4", - // Run: func(cmd *cobra.Command, args []string) { }, } -func init() { - // rootCmd.AddCommand() -} +func init() {} diff --git a/cmd/command/delete.go b/cmd/command/delete.go index d6640e8..499c8f0 100644 --- a/cmd/command/delete.go +++ b/cmd/command/delete.go @@ -14,8 +14,7 @@ import ( var deleteCmd = &cobra.Command{ Use: "delete", Example: "mctl command delete ", - // Short: "Deletes a server", - // Long: `Deletes server configuration`, + Short: "Deletes a command from the database", Run: func(cmd *cobra.Command, args []string) { db, err := database.New() cobra.CheckErr(err) diff --git a/cmd/command/run.go b/cmd/command/run.go new file mode 100644 index 0000000..8ff9a2b --- /dev/null +++ b/cmd/command/run.go @@ -0,0 +1,75 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package command + +import ( + "errors" + "fmt" + "strings" + + "code.jakeyoungdev.com/jake/mctl/client" + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var ( + cfgserver string +) + +var runCmd = &cobra.Command{ + Use: "run", + Example: "mctl command run -s args...", + Short: "Runs a saved command on a server", + Long: `Runs the named command with the provided args on the default/active server unless -s is specified`, + RunE: func(cmd *cobra.Command, args []string) error { + cname := args[0] + + db, err := database.New() + if err != nil { + return err + } + defer db.Close() + + crun, err := db.GetCmd(cname) + if err != nil { + return err + } + + count := strings.Count(crun, "%s") + l := len(args) + if l-1 != count { + return fmt.Errorf("not enough arguments to fill all command placeholders, required: %d - have: %d", count, l) + } + + cli, err := client.New(cfgserver) + cobra.CheckErr(err) + defer cli.Close() + + var fargs []any + for _, x := range args[1:] { + fargs = append(fargs, x) + } + + res, err := cli.Command(fmt.Sprintf(crun, fargs...)) + if err != nil { + return err + } + + fmt.Println(res) + + return nil + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("name parameter required") + } + + return nil + }, +} + +func init() { + runCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server name") + CommandCmd.AddCommand(runCmd) +} diff --git a/cmd/command/view.go b/cmd/command/view.go index 85fb091..ec9006c 100644 --- a/cmd/command/view.go +++ b/cmd/command/view.go @@ -13,7 +13,7 @@ import ( var viewCmd = &cobra.Command{ Use: "view", Example: "mctl command view", - // Short: "view all commands", + Short: "view all saved commands", Run: func(cmd *cobra.Command, args []string) { db, err := database.New() cobra.CheckErr(err) diff --git a/cmd/destroy.go b/cmd/destroy.go index e574e29..c793b9e 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -4,7 +4,10 @@ Copyright © 2025 Jake jake.young.dev@gmail.com package cmd import ( + "bufio" "fmt" + "os" + "strings" "code.jakeyoungdev.com/jake/mctl/database" "github.com/spf13/cobra" @@ -12,18 +15,27 @@ import ( // destroyCmd represents the destroy command var destroyCmd = &cobra.Command{ - Use: "destroy", - // Short: "Clear all configuration", - // Long: `Clears all configuration values for mctl. [WARNING] all server configuration will be lost`, + Use: "destroy", + Short: "clears all configuration", + Long: `clear all data and drop database tables, this will delete all previously saved data and the 'inti' command + must be run again before use`, Run: func(cmd *cobra.Command, args []string) { - db, err := database.New() - cobra.CheckErr(err) - defer db.Close() + scanner := bufio.NewScanner(os.Stdin) + fmt.Printf("Are you sure you want to destroy your config? (yes|no): ") + if scanner.Scan() { + if strings.EqualFold(scanner.Text(), "yes") { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() - err = db.Destroy() - cobra.CheckErr(err) + err = db.Destroy() + cobra.CheckErr(err) - fmt.Println("Configuration is cleared, the 'init' command must be run again.") + fmt.Println("Configuration is cleared, the 'init' command must be run again.") + } + } else { + return + } }, } diff --git a/cmd/init.go b/cmd/init.go index 698a296..b9b144d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -9,12 +9,9 @@ import ( ) var initCmd = &cobra.Command{ - Use: "init", - Example: "mctl init", - SilenceUsage: true, - // Short: "Login to server and send commands", - // Long: `Login to default server and enter command loop. The default server - // is used unless the server flag is set`, + Use: "init", + Example: "mctl init", + Short: "initializes the database tables, must be run before mctl can be used", Run: func(cmd *cobra.Command, args []string) { db, err := database.New() cobra.CheckErr(err) @@ -26,5 +23,5 @@ var initCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(loginCmd) + rootCmd.AddCommand(initCmd) } diff --git a/cmd/login.go b/cmd/login.go index 8ae96ee..a9da7ab 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -32,7 +32,7 @@ var loginCmd = &cobra.Command{ //get default server if server == "" { - ds, err := db.GetServer("default") + ds, err := db.GetActiveServer() cobra.CheckErr(err) server = ds.Name diff --git a/cmd/root.go b/cmd/root.go index 166eecd..5fd9de1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,8 +16,7 @@ var rootCmd = &cobra.Command{ Use: "mctl", Short: "A remote console client", Long: `mctl is a terminal-friendly remote console client made to manage game servers.`, - Version: "v0.3.4", - // Run: func(cmd *cobra.Command, args []string) { }, + Version: "v0.3.4", //change version number } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/cmd/server/active.go b/cmd/server/active.go new file mode 100644 index 0000000..58763c0 --- /dev/null +++ b/cmd/server/active.go @@ -0,0 +1,35 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package server + +import ( + "errors" + + "code.jakeyoungdev.com/jake/mctl/database" + "github.com/spf13/cobra" +) + +var activeCmd = &cobra.Command{ + Use: "active", + Example: "mctl server active ", + Short: "sets the active server to run commands on", + Run: func(cmd *cobra.Command, args []string) { + db, err := database.New() + cobra.CheckErr(err) + defer db.Close() + + err = db.SetActiveServer(args[0]) + cobra.CheckErr(err) + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("server parameter missing") + } + return nil + }, +} + +func init() { + ServerCmd.AddCommand(activeCmd) +} diff --git a/cmd/server/add.go b/cmd/server/add.go index 8f7bbb6..27c2d89 100644 --- a/cmd/server/add.go +++ b/cmd/server/add.go @@ -20,7 +20,7 @@ var addCmd = &cobra.Command{ Use: "add", Example: "mctl server add", Short: "Saves a new server configuration", - Long: `Saves server address, alias, port, and password.`, + Long: `Saves server address, alias, port, and password to the database.`, Run: func(cmd *cobra.Command, args []string) { scanner := bufio.NewScanner(os.Stdin) diff --git a/cmd/server/delete.go b/cmd/server/delete.go index 0b94987..0fac751 100644 --- a/cmd/server/delete.go +++ b/cmd/server/delete.go @@ -13,8 +13,7 @@ import ( var deleteCmd = &cobra.Command{ Use: "delete", Example: "mctl server delete ", - // Short: "Deletes a server", - // Long: `Deletes server configuration`, + Short: "deletes a server from the database", Run: func(cmd *cobra.Command, args []string) { db, err := database.New() cobra.CheckErr(err) diff --git a/cmd/server/server.go b/cmd/server/server.go index 1c07002..d2bc87e 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -10,12 +10,6 @@ import ( var ServerCmd = &cobra.Command{ Use: "server", Example: "mctl server ", - // Short: "A remote console client", - // Long: `mctl is a terminal-friendly remote console client made to manage game servers.`, - // Version: "v0.3.4", - // Run: func(cmd *cobra.Command, args []string) { }, } -func init() { - // rootCmd.AddCommand() -} +func init() {} diff --git a/cmd/server/update.go b/cmd/server/update.go index dfa68cf..f2782c8 100644 --- a/cmd/server/update.go +++ b/cmd/server/update.go @@ -17,8 +17,7 @@ import ( var updateCmd = &cobra.Command{ Use: "update", Example: "mctl server update ", - // Short: "Saves a new server configuration", - // Long: `Saves server address, alias, port, and password.`, + Short: "updates a saved servers password in the database", Run: func(cmd *cobra.Command, args []string) { //read in password using term to keep it secure/hidden from bash history fmt.Printf("Password: ") diff --git a/cmd/server/view.go b/cmd/server/view.go index 34516dd..f54c5ed 100644 --- a/cmd/server/view.go +++ b/cmd/server/view.go @@ -13,7 +13,7 @@ import ( var viewCmd = &cobra.Command{ Use: "view", Example: "mctl server view", - Short: "view all servers", + Short: "view all saved servers", Run: func(cmd *cobra.Command, args []string) { db, err := database.New() cobra.CheckErr(err) @@ -27,7 +27,8 @@ var viewCmd = &cobra.Command{ fmt.Printf("Name: %s\n", s.Name) fmt.Printf("Address: %s\n", s.Server) fmt.Printf("Port: %d\n", s.Port) - fmt.Printf("Password: %s\n", s.Password) + fmt.Println("Password: [REDACTED]") + fmt.Printf("Default: %t\n", s.Default) } }, } diff --git a/database/sqlx.go b/database/sqlx.go index 159bc95..f3993a8 100644 --- a/database/sqlx.go +++ b/database/sqlx.go @@ -1,8 +1,11 @@ package database import ( + "context" + "database/sql" "fmt" "os" + "time" "code.jakeyoungdev.com/jake/mctl/model" "github.com/jmoiron/sqlx" @@ -11,9 +14,14 @@ import ( ) /* -all sqlx methods for CRUD functionalities of commands and servers +all sqlx methods for CRUD functionalities of commands and servers. All database methods should use the internal +methods query, exec, queryrow -- these wrapper functions handle timeouts and other configuration */ +const ( + DB_TIMEOUT = time.Second * 1 +) + type database struct { *sqlx.DB } @@ -22,6 +30,11 @@ type Database interface { Init() error Destroy() error Close() error + //internals + timeout() (context.Context, context.CancelFunc) + query(query string, args ...any) (*sqlx.Rows, error) + queryRow(query string, args ...any) *sqlx.Row + exec(query string, args ...any) (sql.Result, error) //command methods GetCmd(name string) (string, error) GetAllCmds() ([]model.Command, error) @@ -30,7 +43,9 @@ type Database interface { DeleteCmd(name string) error //server methods GetServer(name string) (model.Server, error) + GetActiveServer() (model.Server, error) GetAllServers() ([]model.Server, error) + SetActiveServer(name string) error SaveServer(srv model.Server) error UpdateServer(name, password string) error DeleteServer(name string) error @@ -65,12 +80,12 @@ func (d *database) Init() error { name TEXT PRIMARY KEY, server TEXT, password TEXT, - port NUMBER, - --active TEXT? bit? or something + port INTEGER, + active INTEGER NOT NULL DEFAULT 0 ); ` - _, err := d.Exec(query) + _, err := d.exec(query) return err } @@ -81,7 +96,7 @@ func (d *database) Destroy() error { DROP TABLE servers; ` - _, err := d.Exec(query) + _, err := d.exec(query) return err } @@ -89,6 +104,31 @@ func (d *database) Close() error { return d.DB.Close() } +func (d *database) timeout() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), DB_TIMEOUT) +} + +func (d *database) query(query string, args ...any) (*sqlx.Rows, error) { + ctx, cl := d.timeout() + defer cl() + + return d.QueryxContext(ctx, query, args...) +} + +func (d *database) queryRow(query string, args ...any) *sqlx.Row { + ctx, cl := d.timeout() + defer cl() + + return d.QueryRowxContext(ctx, query, args...) +} + +func (d *database) exec(query string, args ...any) (sql.Result, error) { + ctx, cl := d.timeout() + defer cl() + + return d.ExecContext(ctx, query, args...) +} + // gets command using name func (d *database) GetCmd(name string) (string, error) { query := ` @@ -101,7 +141,7 @@ func (d *database) GetCmd(name string) (string, error) { ` var cmd string - err := d.QueryRowx(query, name).Scan(&cmd) + err := d.queryRow(query, name).Scan(&cmd) if err != nil { return "", err } @@ -119,7 +159,7 @@ func (d *database) GetAllCmds() ([]model.Command, error) { commands ` - rows, err := d.Queryx(query) + rows, err := d.query(query) if err != nil { return nil, err } @@ -146,7 +186,7 @@ func (d *database) SaveCmd(name, cmd string) error { VALUES(?, ?) ` - _, err := d.Exec(query, name, cmd) + _, err := d.exec(query, name, cmd) return err } @@ -161,7 +201,7 @@ func (d *database) UpdateCmd(name, cmd string) error { name = ? ` - _, err := d.Exec(query, cmd, name) + _, err := d.exec(query, cmd, name) return err } @@ -171,7 +211,7 @@ func (d *database) DeleteCmd(name string) error { WHERE name = ? ` - _, err := d.Exec(query, name) + _, err := d.exec(query, name) return err } @@ -189,13 +229,31 @@ func (d *database) GetServer(name string) (model.Server, error) { ` var s model.Server - err := d.QueryRowx(query, name).StructScan(&s) + err := d.queryRow(query, name).StructScan(&s) if err != nil { return model.Server{}, err } return s, nil } +func (d *database) GetActiveServer() (model.Server, error) { + query := ` + SELECT + name, + server, + password, + port + FROM + servers + WHERE + active = 1 + ` + + var s model.Server + err := d.queryRow(query).StructScan(&s) + return s, err +} + func (d *database) GetAllServers() ([]model.Server, error) { query := ` SELECT @@ -207,7 +265,7 @@ func (d *database) GetAllServers() ([]model.Server, error) { servers ` - rows, err := d.Queryx(query) + rows, err := d.query(query) if err != nil { return nil, err } @@ -233,7 +291,26 @@ func (d *database) SaveServer(srv model.Server) error { VALUES(?, ?, ?, ?) ` - _, err := d.Exec(query, srv.Name, srv.Server, srv.Password, srv.Port) + _, err := d.exec(query, srv.Name, srv.Server, srv.Password, srv.Port) + return err +} + +func (d *database) SetActiveServer(name string) error { + query := ` + UPDATE + servers + SET + active = 0; + + UPDATE + servers + SET + active = 1 + WHERE + name = ? + ` + + _, err := d.exec(query, name) return err } @@ -247,7 +324,7 @@ func (d *database) UpdateServer(name, password string) error { name = ? ` - _, err := d.Exec(query, password, name) + _, err := d.exec(query, password, name) return err } @@ -258,6 +335,6 @@ func (d *database) DeleteServer(name string) error { name = ? ` - _, err := d.Exec(query, name) + _, err := d.exec(query, name) return err } diff --git a/model/data.go b/model/data.go index 71c3d9d..ae87d52 100644 --- a/model/data.go +++ b/model/data.go @@ -10,4 +10,5 @@ type Server struct { Server string `db:"server"` Password string `db:"password"` Port int `db:"port"` + Default bool `db:"active"` }