mctl/database/sqlx.go
jake 7ff43c82c2 [fix] Testing fixes
- sqlite error handling and wrapping
- more responsive commands
- database updates
2025-06-19 17:23:17 -04:00

366 lines
6.3 KiB
Go

package database
import (
"context"
"database/sql"
"errors"
"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.
*/
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)
//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)
ctx, cl := d.timeout()
defer cl()
_, err := d.ExecContext(ctx, query)
return err
}
// drops commands and servers tables
func (d *database) Destroy() error {
query := `
DROP TABLE commands;
DROP TABLE servers;
`
ctx, cl := d.timeout()
defer cl()
_, err := d.ExecContext(ctx, 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)
}
// gets command using name
func (d *database) GetCmd(name string) (string, error) {
query := `
SELECT
command
FROM
commands
WHERE
name = ?
`
var cmd string
ctx, cl := d.timeout()
defer cl()
err := d.QueryRowxContext(ctx, query, name).Scan(&cmd)
if errors.Is(err, sql.ErrNoRows) {
return "", errors.New("Command not found")
}
if err != nil {
return "", err
}
return cmd, nil
}
// gets all saved commands
func (d *database) GetAllCmds() ([]model.Command, error) {
query := `
SELECT
name,
command
FROM
commands
`
ctx, cancel := d.timeout()
defer cancel()
rows, err := d.QueryxContext(ctx, 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(?, ?)
`
ctx, cancel := d.timeout()
defer cancel()
_, err := d.ExecContext(ctx, query, name, cmd)
return err
}
// DO WE NEED THIS?
func (d *database) UpdateCmd(name, cmd string) error {
query := `
UPDATE
commands
SET
cmd = ?
WHERE
name = ?
`
ctx, cancel := d.timeout()
defer cancel()
_, err := d.ExecContext(ctx, query, cmd, name)
return err
}
func (d *database) DeleteCmd(name string) error {
query := `
DELETE FROM commands
WHERE name = ?
`
ctx, cancel := d.timeout()
defer cancel()
_, err := d.ExecContext(ctx, query, name)
return err
}
func (d *database) GetServer(name string) (model.Server, error) {
query := `
SELECT
name,
server,
password,
port,
active
FROM
servers
WHERE
name = ?
`
ctx, cancel := d.timeout()
defer cancel()
var s model.Server
err := d.QueryRowxContext(ctx, query, name).StructScan(&s)
if errors.Is(err, sql.ErrNoRows) {
return model.Server{}, errors.New("Server not found")
}
if err != nil {
return model.Server{}, err
}
return s, nil
}
func (d *database) GetActiveServer() (model.Server, error) {
query := `
SELECT
name,
server,
password,
port,
active
FROM
servers
WHERE
active = 1
`
ctx, cancel := d.timeout()
defer cancel()
var s model.Server
err := d.QueryRowxContext(ctx, query).StructScan(&s)
if errors.Is(err, sql.ErrNoRows) {
return s, errors.New("No active server set")
}
return s, err
}
func (d *database) GetAllServers() ([]model.Server, error) {
query := `
SELECT
name,
server,
password,
port,
active
FROM
servers
`
ctx, cancel := d.timeout()
defer cancel()
rows, err := d.QueryxContext(ctx, 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(?, ?, ?, ?)
`
ctx, cancel := d.timeout()
defer cancel()
_, err := d.ExecContext(ctx, query, srv.Name, srv.Server, srv.Password, srv.Port)
return err
}
func (d *database) SetActiveServer(name string) error {
clear := `
UPDATE
servers
SET
active = 0
`
clrctx, clrcancel := d.timeout()
_, err := d.ExecContext(clrctx, clear)
if err != nil {
return err
}
defer clrcancel()
update := `
UPDATE
servers
SET
active = 1
WHERE
name = ?
`
ctx, cancel := d.timeout()
defer cancel()
_, err = d.ExecContext(ctx, update, 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 = ?
`
ctx, cancel := d.timeout()
defer cancel()
_, err := d.ExecContext(ctx, query, password, name)
return err
}
func (d *database) DeleteServer(name string) error {
query := `
DELETE FROM servers
WHERE
name = ?
`
ctx, cancel := d.timeout()
defer cancel()
_, err := d.ExecContext(ctx, query, name)
return err
}