feat(store): per-account inbound/outbound whitelist CRUD
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Direction string
|
||||
|
||||
const (
|
||||
DirIn Direction = "in"
|
||||
DirOut Direction = "out"
|
||||
)
|
||||
|
||||
func (d Direction) table() (string, error) {
|
||||
switch d {
|
||||
case DirIn:
|
||||
return "whitelist_in", nil
|
||||
case DirOut:
|
||||
return "whitelist_out", nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid direction %q", d)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) accountID(name string) (int64, error) {
|
||||
a, err := s.GetAccount(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return a.ID, nil
|
||||
}
|
||||
|
||||
func (s *Store) AddWhitelist(account string, dir Direction, address string) error {
|
||||
tbl, err := dir.table()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := s.accountID(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
fmt.Sprintf("INSERT OR IGNORE INTO %s(account_id,address) VALUES(?,?)", tbl),
|
||||
id, strings.ToLower(address))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) RemoveWhitelist(account string, dir Direction, address string) error {
|
||||
tbl, err := dir.table()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := s.accountID(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
fmt.Sprintf("DELETE FROM %s WHERE account_id=? AND address=?", tbl),
|
||||
id, strings.ToLower(address))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) ListWhitelist(account string, dir Direction) ([]string, error) {
|
||||
tbl, err := dir.table()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := s.accountID(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.db.Query(
|
||||
fmt.Sprintf("SELECT address FROM %s WHERE account_id=? ORDER BY address", tbl), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := []string{}
|
||||
for rows.Next() {
|
||||
var a string
|
||||
if err := rows.Scan(&a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, a)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWhitelistAddListRemove(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
_, _ = s.AddAccount(sampleAccount())
|
||||
|
||||
if err := s.AddWhitelist("work", DirIn, "Bob@Example.com"); err != nil {
|
||||
t.Fatalf("add: %v", err)
|
||||
}
|
||||
_ = s.AddWhitelist("work", DirIn, "@trusted.com")
|
||||
|
||||
got, err := s.ListWhitelist("work", DirIn)
|
||||
if err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
want := []string{"@trusted.com", "bob@example.com"} // lower-cased, sorted
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got %v want %v", got, want)
|
||||
}
|
||||
|
||||
// Out list is independent.
|
||||
if out, _ := s.ListWhitelist("work", DirOut); len(out) != 0 {
|
||||
t.Fatalf("out list should be empty, got %v", out)
|
||||
}
|
||||
|
||||
if err := s.RemoveWhitelist("work", DirIn, "bob@example.com"); err != nil {
|
||||
t.Fatalf("remove: %v", err)
|
||||
}
|
||||
got, _ = s.ListWhitelist("work", DirIn)
|
||||
if !reflect.DeepEqual(got, []string{"@trusted.com"}) {
|
||||
t.Fatalf("after remove got %v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user