package database import ( "context" "database/sql" "fmt" "os" "time" "code.jakeyoungdev.com/jake/mctl/model" "github.com/jmoiron/sqlx" _ "github.com/ncruces/go-sqlite3/driver" _ "github.com/ncruces/go-sqlite3/embed" ) /* 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 * 10 ) type database struct { *sqlx.DB } 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) SaveCmd(name, cmd string) error UpdateCmd(name, cmd string) error 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 } // creates a new sqlite connection to the mctl database. Database files are // kept in the the user home directory func New() (Database, error) { home, err := os.UserHomeDir() if err != nil { return nil, err } db, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s/.mctl", home)) if err != nil { return nil, err } return &database{ db, }, nil } // intitial database setup, creates commands and servers tables func (d *database) Init() error { query := ` CREATE TABLE IF NOT EXISTS commands( name TEXT PRIMARY KEY, command TEXT ); CREATE TABLE IF NOT EXISTS servers( name TEXT PRIMARY KEY, server TEXT, password TEXT, port INTEGER, active INTEGER NOT NULL DEFAULT 0 ); ` _, err := d.exec(query) return err } // drops commands and servers tables func (d *database) Destroy() error { query := ` DROP TABLE commands; DROP TABLE servers; ` _, err := d.exec(query) return err } 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 := ` SELECT command FROM commands WHERE name = ? ` var cmd string err := d.queryRow(query, name).Scan(&cmd) if err != nil { return "", err } return name, nil } // gets all saved commands func (d *database) GetAllCmds() ([]model.Command, error) { query := ` SELECT name, command FROM commands ` rows, err := d.query(query) if err != nil { return nil, err } defer rows.Close() var res []model.Command for rows.Next() { var r model.Command err := rows.StructScan(&r) if err != nil { return nil, err } res = append(res, r) } return res, nil } // save a new command func (d *database) SaveCmd(name, cmd string) error { query := ` INSERT INTO commands(name, command) VALUES(?, ?) ` _, err := d.exec(query, name, cmd) return err } // DO WE NEED THIS? func (d *database) UpdateCmd(name, cmd string) error { query := ` UPDATE commands SET cmd = ? WHERE name = ? ` _, err := d.exec(query, cmd, name) return err } func (d *database) DeleteCmd(name string) error { query := ` DELETE FROM commands WHERE name = ? ` _, err := d.exec(query, name) return err } func (d *database) GetServer(name string) (model.Server, error) { query := ` SELECT name, server, password, port FROM servers WHERE name = ? ` var s model.Server 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 name, server, password, port FROM servers ` rows, err := d.query(query) if err != nil { return nil, err } defer rows.Close() var res []model.Server for rows.Next() { var r model.Server err := rows.StructScan(&r) if err != nil { return nil, err } res = append(res, r) } return res, nil } func (d *database) SaveServer(srv model.Server) error { query := ` INSERT INTO servers(name, server, password, port) VALUES(?, ?, ?, ?) ` _, 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 } // updates server password, if anymore fields need updated the entry should be deleted and recreated func (d *database) UpdateServer(name, password string) error { query := ` UPDATE servers SET password = ? WHERE name = ? ` _, err := d.exec(query, password, name) return err } func (d *database) DeleteServer(name string) error { query := ` DELETE FROM servers WHERE name = ? ` _, err := d.exec(query, name) return err }