Add actions to items

This commit is contained in:
Tim Van Baak 2025-01-29 08:48:12 -08:00
parent d23efdf00b
commit f804299180
7 changed files with 93 additions and 30 deletions

View File

@ -147,4 +147,36 @@ func TestExecute(t *testing.T) {
res, err = execute([]string{"jq", "-cn", `["a", "a"] | .[] | {id: .}`}) res, err = execute([]string{"jq", "-cn", `["a", "a"] | .[] | {id: .}`})
assertNil(err) assertNil(err)
assertLen(res, 2) assertLen(res, 2)
// Action keys are detected even with empty values
res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": null}}`})
assertNil(err)
assertLen(res, 1)
if res[0].Action["hello"] == nil {
t.Fatal("missing hello action")
}
if res[0].Action["goodbye"] != nil {
t.Fatal("nonexistent action should key to nil in Action")
}
res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": ""}}`})
assertNil(err)
assertLen(res, 1)
if res[0].Action["hello"] == nil {
t.Fatal("missing hello action")
}
res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": []}}`})
assertNil(err)
assertLen(res, 1)
if res[0].Action["hello"] == nil {
t.Fatal("missing hello action")
}
res, err = execute([]string{"jq", "-cn", `{id: "test", action: {"hello": {}}}`})
assertNil(err)
assertLen(res, 1)
if res[0].Action["hello"] == nil {
t.Fatal("missing hello action")
}
} }

View File

@ -17,8 +17,8 @@ func TestDeleteSourceCascade(t *testing.T) {
t.Fatalf("failed to add source2: %v", err) t.Fatalf("failed to add source2: %v", err)
} }
if err := AddItems(db, []Item{ if err := AddItems(db, []Item{
{"source1", "item1", 0, true, "", "", "", "", 0}, {"source1", "item1", 0, true, "", "", "", "", 0, nil},
{"source2", "item2", 0, true, "", "", "", "", 0}, {"source2", "item2", 0, true, "", "", "", "", 0, nil},
}); err != nil { }); err != nil {
t.Fatalf("failed to add items: %v", err) t.Fatalf("failed to add items: %v", err)
} }
@ -42,7 +42,7 @@ func TestDeleteSourceCascade(t *testing.T) {
t.Fatalf("Expected only 1 item after source delete, got %d", len(items)) t.Fatalf("Expected only 1 item after source delete, got %d", len(items))
} }
err = AddItems(db, []Item{{"source1", "item3", 0, true, "", "", "", "", 0}}) err = AddItems(db, []Item{{"source1", "item3", 0, true, "", "", "", "", 0, nil}})
if err == nil { if err == nil {
t.Fatal("Unexpected success adding item for nonexistent source") t.Fatal("Unexpected success adding item for nonexistent source")
} }

View File

@ -1,11 +1,22 @@
package core package core
import ( import (
"database/sql/driver"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
) )
type Actions map[string]json.RawMessage
func (a Actions) Value() (driver.Value, error) {
return json.Marshal(a)
}
func (a *Actions) Scan(value interface{}) error {
return json.Unmarshal([]byte(value.(string)), a)
}
type Item struct { type Item struct {
Source string `json:"source"` Source string `json:"source"`
Id string `json:"id"` Id string `json:"id"`
@ -16,6 +27,7 @@ type Item struct {
Body string `json:"body"` Body string `json:"body"`
Link string `json:"link"` Link string `json:"link"`
Time int `json:"time"` Time int `json:"time"`
Action Actions `json:"action"`
} }
// Whether an item that no longer appears in a fetch can be deleted. // Whether an item that no longer appears in a fetch can be deleted.

View File

@ -1,6 +1,9 @@
package core package core
import "testing" import (
"encoding/json"
"testing"
)
func TestItemFormatsExist(t *testing.T) { func TestItemFormatsExist(t *testing.T) {
for name := range AvailableFormats { for name := range AvailableFormats {
@ -30,6 +33,9 @@ func TestItemRoundTrip(t *testing.T) {
Body: "body", Body: "body",
Link: "link", Link: "link",
Time: 123456, Time: 123456,
Action: map[string]json.RawMessage{
"hello": json.RawMessage(`"world"`),
},
} }
if err := AddItems(db, []Item{item1}); err != nil { if err := AddItems(db, []Item{item1}); err != nil {
t.Fatalf("update failed: %v", err) t.Fatalf("update failed: %v", err)

View File

@ -51,16 +51,20 @@ func DeleteSource(db *DB, name string) error {
func AddItems(db *DB, items []Item) error { func AddItems(db *DB, items []Item) error {
return db.Transact(func(tx *sql.Tx) error { return db.Transact(func(tx *sql.Tx) error {
stmt, err := tx.Prepare(` stmt, err := tx.Prepare(`
insert into items (source, id, active, title, author, body, link, time) insert into items (source, id, active, title, author, body, link, time, action)
values (?, ?, ?, ?, ?, ?, ?, ?) values (?, ?, ?, ?, ?, ?, ?, ?, jsonb(?))
`) `)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to prepare insert: %v", err)
} }
for _, item := range items { for _, item := range items {
_, err = stmt.Exec(item.Source, item.Id, true, item.Title, item.Author, item.Body, item.Link, item.Time) actions, err := json.Marshal(item.Action)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to marshal actions for %s/%s: %v", item.Source, item.Id, err)
}
_, err = stmt.Exec(item.Source, item.Id, true, item.Title, item.Author, item.Body, item.Link, item.Time, actions)
if err != nil {
return fmt.Errorf("failed to insert %s/%s: %v", item.Source, item.Id, err)
} }
} }
return nil return nil
@ -100,7 +104,8 @@ func UpdateItems(db *DB, items []Item) error {
author = ?, author = ?,
body = ?, body = ?,
link = ?, link = ?,
time = ? time = ?,
action = jsonb(?)
where source = ? where source = ?
and id = ? and id = ?
`) `)
@ -108,7 +113,11 @@ func UpdateItems(db *DB, items []Item) error {
return err return err
} }
for _, item := range items { for _, item := range items {
_, err = stmt.Exec(item.Title, item.Author, item.Body, item.Link, item.Time, item.Source, item.Id) actions, err := json.Marshal(item.Action)
if err != nil {
return fmt.Errorf("failed to marshal actions for %s/%s: %v", item.Source, item.Id, err)
}
_, err = stmt.Exec(item.Title, item.Author, item.Body, item.Link, item.Time, actions, item.Source, item.Id)
if err != nil { if err != nil {
return err return err
} }
@ -161,7 +170,7 @@ func getItems(db *DB, query string, args ...any) ([]Item, error) {
var items []Item var items []Item
for rows.Next() { for rows.Next() {
var item Item var item Item
err = rows.Scan(&item.Source, &item.Id, &item.Created, &item.Active, &item.Title, &item.Author, &item.Body, &item.Link, &item.Time) err = rows.Scan(&item.Source, &item.Id, &item.Created, &item.Active, &item.Title, &item.Author, &item.Body, &item.Link, &item.Time, &item.Action)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -172,7 +181,7 @@ func getItems(db *DB, query string, args ...any) ([]Item, error) {
func GetItem(db *DB, source string, id string) (Item, error) { func GetItem(db *DB, source string, id string) (Item, error) {
items, err := getItems(db, ` items, err := getItems(db, `
select source, id, created, active, title, author, body, link, time select source, id, created, active, title, author, body, link, time, json(action)
from items from items
where source = ? where source = ?
and id = ? and id = ?
@ -189,7 +198,7 @@ func GetItem(db *DB, source string, id string) (Item, error) {
func GetAllActiveItems(db *DB) ([]Item, error) { func GetAllActiveItems(db *DB) ([]Item, error) {
return getItems(db, ` return getItems(db, `
select select
source, id, created, active, title, author, body, link, time source, id, created, active, title, author, body, link, time, json(action)
from items from items
where active <> 0 where active <> 0
`) `)
@ -198,7 +207,7 @@ func GetAllActiveItems(db *DB) ([]Item, error) {
func GetAllItems(db *DB) ([]Item, error) { func GetAllItems(db *DB) ([]Item, error) {
return getItems(db, ` return getItems(db, `
select select
source, id, created, active, title, author, body, link, time source, id, created, active, title, author, body, link, time, json(action)
from items from items
`) `)
} }
@ -206,7 +215,7 @@ func GetAllItems(db *DB) ([]Item, error) {
func GetActiveItemsForSource(db *DB, source string) ([]Item, error) { func GetActiveItemsForSource(db *DB, source string) ([]Item, error) {
return getItems(db, ` return getItems(db, `
select select
source, id, created, active, title, author, body, link, time source, id, created, active, title, author, body, link, time, json(action)
from items from items
where where
source = ? source = ?
@ -217,7 +226,7 @@ func GetActiveItemsForSource(db *DB, source string) ([]Item, error) {
func GetAllItemsForSource(db *DB, source string) ([]Item, error) { func GetAllItemsForSource(db *DB, source string) ([]Item, error) {
return getItems(db, ` return getItems(db, `
select select
source, id, created, active, title, author, body, link, time source, id, created, active, title, author, body, link, time, json(action)
from items from items
where where
source = ? source = ?
@ -225,6 +234,8 @@ func GetAllItemsForSource(db *DB, source string) ([]Item, error) {
} }
// Given the results of a fetch, add new items, update existing items, and delete expired items. // Given the results of a fetch, add new items, update existing items, and delete expired items.
//
// Returns the number of new and deleted items on success.
func UpdateWithFetchedItems(db *DB, source string, items []Item) (int, int, error) { func UpdateWithFetchedItems(db *DB, source string, items []Item) (int, int, error) {
// Get the existing items // Get the existing items
existingItems, err := GetAllItemsForSource(db, source) existingItems, err := GetAllItemsForSource(db, source)

View File

@ -61,8 +61,8 @@ func TestAddItem(t *testing.T) {
} }
if err := AddItems(db, []Item{ if err := AddItems(db, []Item{
{"test", "one", 0, true, "", "", "", "", 0}, {"test", "one", 0, true, "", "", "", "", 0, nil},
{"test", "two", 0, true, "title", "author", "body", "link", 123456}, {"test", "two", 0, true, "title", "author", "body", "link", 123456, nil},
}); err != nil { }); err != nil {
t.Fatalf("failed to add items: %v", err) t.Fatalf("failed to add items: %v", err)
} }
@ -219,8 +219,9 @@ func TestOnCreateAction(t *testing.T) {
if add != 1 || err != nil { if add != 1 || err != nil {
t.Fatal("failed update with alter oncreate") t.Fatal("failed update with alter oncreate")
} }
if getItem("two").Title != "Goodbye, World" { two := getItem("two")
t.Fatal("title not updated") if two.Title != "Goodbye, World" {
t.Fatalf("title not updated, is: %s", two.Title)
} }
// on_create can add a field // on_create can add a field

View File

@ -19,6 +19,7 @@ create table items(
body text, body text,
link text, link text,
time int, time int,
action blob,
primary key (source, id), primary key (source, id),
foreign key (source) references sources (name) on delete cascade foreign key (source) references sources (name) on delete cascade
) strict; ) strict;