From fb0d4e9aeeee0beef8258ee14dd0ac20f9e1c039 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 20 Jan 2025 19:53:22 -0800 Subject: [PATCH] Add actions to the database --- core/action.go | 72 ++++++++++++++++++++++++++++++++ core/action_test.go | 45 ++++++++++++++++++++ core/sql/0001_initial_schema.sql | 7 ++++ 3 files changed, 124 insertions(+) diff --git a/core/action.go b/core/action.go index 4abf981..ec2de34 100644 --- a/core/action.go +++ b/core/action.go @@ -3,6 +3,7 @@ package core import ( "bufio" "context" + "database/sql/driver" "encoding/json" "errors" "io" @@ -13,6 +14,77 @@ import ( "time" ) +// Type alias for storing string array as jsonb +type argList []string + +func (a argList) Value() (driver.Value, error) { + return json.Marshal(a) +} + +func (a *argList) Scan(value interface{}) error { + return json.Unmarshal([]byte(value.(string)), a) +} + +func AddAction(db *DB, source string, name string, argv []string) error { + _, err := db.Exec(` + insert into actions (source, name, argv) + values (?, ?, jsonb(?)) + `, source, name, argList(argv)) + return err +} + +func UpdateAction(db *DB, source string, name string, argv []string) error { + _, err := db.Exec(` + update actions + set argv = jsonb(?) + where source = ? and name = ? + `, argList(argv), source, name) + return err +} + +func GetActionsForSource(db *DB, source string) ([]string, error) { + rows, err := db.Query(` + select name + from actions + where source = ? + `, source) + if err != nil { + return nil, err + } + var names []string + for rows.Next() { + var name string + err = rows.Scan(&name) + if err != nil { + return nil, err + } + names = append(names, name) + } + return names, nil +} + +func GetArgvForAction(db *DB, source string, name string) ([]string, error) { + rows := db.QueryRow(` + select json(argv) + from actions + where source = ? and name = ? + `, source, name) + var argv argList + err := rows.Scan(&argv) + if err != nil { + return nil, err + } + return argv, nil +} + +func DeleteAction(db *DB, source string, name string) error { + _, err := db.Exec(` + delete from actions + where source = ? and name = ? + `) + return err +} + func readStdout(stdout io.ReadCloser, items chan Item, cparse chan bool) { var item Item parseError := false diff --git a/core/action_test.go b/core/action_test.go index f6ce4d8..021e0b6 100644 --- a/core/action_test.go +++ b/core/action_test.go @@ -5,6 +5,51 @@ import ( "time" ) +func TestActionCreate(t *testing.T) { + db := EphemeralDb(t) + + if err := AddAction(db, "test", "hello", []string{"echo", "hello"}); err == nil { + t.Fatal("Action created for nonexistent source") + } + + if err := AddSource(db, "test"); err != nil { + t.Fatal(err) + } + + if err := AddAction(db, "test", "hello", []string{"echo", "hello"}); err != nil { + t.Fatal(err) + } + if err := AddAction(db, "test", "goodbye", []string{"exit", "1"}); err != nil { + t.Fatal(err) + } + if err := UpdateAction(db, "test", "goodbye", []string{"echo", "goodbye"}); err != nil { + t.Fatal(err) + } + + actions, err := GetActionsForSource(db, "test") + if err != nil { + t.Fatal(err) + } + if len(actions) != 2 { + t.Fatal("expected 2 actions") + } + found := make(map[string]bool) + for _, action := range actions { + found[action] = true + } + if !found["hello"] || !found["goodbye"] { + t.Fatalf("missing hello and/or goodbye, got: %v", actions) + } + + argv, err := GetArgvForAction(db, "test", "goodbye") + if err != nil { + t.Fatal(err) + } + if len(argv) != 2 || argv[0] != "echo" || argv[1] != "goodbye" { + t.Fatalf("expected [echo goodbye], got: %v", argv) + } +} + func TestExecute(t *testing.T) { assertLen := func(items []Item, length int) { if len(items) != length { diff --git a/core/sql/0001_initial_schema.sql b/core/sql/0001_initial_schema.sql index d6e5d47..2e299d1 100644 --- a/core/sql/0001_initial_schema.sql +++ b/core/sql/0001_initial_schema.sql @@ -2,6 +2,13 @@ create table sources( name text not null, primary key (name) ) strict; +create table actions( + source text not null, + name text not null, + argv blob not null, + primary key (source, name), + foreign key (source) references sources (name) on delete cascade +) strict; create table items( source text not null, id text not null,