diff --git a/core/source.go b/core/source.go new file mode 100644 index 0000000..8f61f7b --- /dev/null +++ b/core/source.go @@ -0,0 +1,109 @@ +package core + +import ( + "database/sql" + "errors" + "fmt" + + _ "github.com/mattn/go-sqlite3" +) + +func AddSource(db *sql.DB, name string) error { + _, err := db.Exec(` + insert into sources (name) + values (?) + `, name) + + return err +} + +func DeleteSource(db *sql.DB, name string) error { + _, err := db.Exec(` + delete from sources + where name = ? + `, name) + + return err +} + +func AddItem( + db *sql.DB, + source string, + id string, + title string, + author string, + body string, + link string, + time int, +) error { + _, err := db.Exec(` + insert into items (source, id, active, title, author, body, link, time) + values (?, ?, ?, ?, ?, ?, ?, ?) + `, source, id, true, title, author, body, link, time) + + return err +} + +// Deactivate an item, returning its previous active state. +func DeactivateItem(db *sql.DB, source string, id string) (bool, error) { + row := db.QueryRow(` + select active + from items + where source = ? and id = ? + `, source, id) + var active bool + err := row.Scan(&active) + if err != nil && errors.Is(err, sql.ErrNoRows) { + return false, fmt.Errorf("item %s/%s not found", source, id) + } + + _, err = db.Exec(` + update items + set active = 0 + where source = ? and id = ? + `, source, id) + if err != nil { + return false, err + } + return active, nil +} + +func GetAllActiveItems(db *sql.DB) ([]Item, error) { + rows, err := db.Query(` + select + source, id, created, active, title, author, body, link, time + from items + where active <> 0 + `) + if err != nil { + return nil, err + } + var items []Item + for rows.Next() { + var item Item + rows.Scan(&item.Source, &item.Id, &item.Created, &item.Active, &item.Title, &item.Author, &item.Body, &item.Link, &item.Time) + items = append(items, item) + } + return items, nil +} + +func GetActiveItemsForSource(db *sql.DB, source string) ([]Item, error) { + rows, err := db.Query(` + select + source, id, created, active, title, author, body, link, time + from items + where + source = ? + and active <> 0 + `, source) + if err != nil { + return nil, err + } + var items []Item + for rows.Next() { + var item Item + rows.Scan(&item.Source, &item.Id, &item.Created, &item.Active, &item.Title, &item.Author, &item.Body, &item.Link, &item.Time) + items = append(items, item) + } + return items, nil +} diff --git a/core/source_test.go b/core/source_test.go new file mode 100644 index 0000000..ed34bbf --- /dev/null +++ b/core/source_test.go @@ -0,0 +1,96 @@ +package core + +import ( + "fmt" + "slices" + "testing" + + _ "github.com/mattn/go-sqlite3" +) + +func TestCreateSource(t *testing.T) { + db := EphemeralDb(t) + defer db.Close() + + if err := AddSource(db, "one"); err != nil { + t.Fatal(err) + } + if err := AddSource(db, "two"); err != nil { + t.Fatal(err) + } + if err := AddSource(db, "three"); err != nil { + t.Fatal(err) + } + if err := DeleteSource(db, "two"); err != nil { + t.Fatal(err) + } + + rows, err := db.Query("select name from sources") + if err != nil { + t.Fatal(err) + } + var names []string + expected := []string{"one", "three"} + for rows.Next() { + var name string + rows.Scan(&name) + names = append(names, name) + } + for i := 0; i < len(expected); i += 1 { + if !slices.Contains(names, expected[i]) { + t.Fatalf("missing %s, have: %v", expected[i], names) + } + } +} + +func AssertItemIs(t *testing.T, item Item, expected string) { + actual := fmt.Sprintf( + "%s/%s/%t/%s/%s/%s/%s/%d", + item.Source, + item.Id, + item.Active, + item.Title, + item.Author, + item.Body, + item.Link, + item.Time, + ) + if actual != expected { + t.Fatalf("expected %s, got %s", expected, actual) + } +} + +func TestAddItem(t *testing.T) { + db := EphemeralDb(t) + defer db.Close() + if err := AddSource(db, "test"); err != nil { + t.Fatal(err) + } + + if err := AddItem(db, "test", "one", "", "", "", "", 0); err != nil { + t.Fatal(err) + } + if err := AddItem(db, "test", "two", "title", "author", "body", "link", 123456); err != nil { + t.Fatal(err) + } + items, err := GetActiveItemsForSource(db, "test") + if err != nil { + t.Fatal(err) + } + if len(items) != 2 { + t.Fatal("should get two items") + } + AssertItemIs(t, items[0], "test/one/true/////0") + AssertItemIs(t, items[1], "test/two/true/title/author/body/link/123456") + + if _, err = DeactivateItem(db, "test", "one"); err != nil { + t.Fatal(err) + } + items, err = GetActiveItemsForSource(db, "test") + if err != nil { + t.Fatal(err) + } + if len(items) != 1 { + t.Fatal("should get one item") + } +}