grapevine.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"bytes"
   7  
   8  	"github.com/dgraph-io/badger/v4"
   9  )
  10  
  11  // GrapeVineStore provides database operations for GrapeVine WoT scores.
  12  // It stores raw JSON blobs without importing the grapevine package to avoid circular deps.
  13  type GrapeVineStore struct {
  14  	*D
  15  }
  16  
  17  // NewGrapeVineStore creates a new GrapeVineStore instance.
  18  func NewGrapeVineStore(db *D) *GrapeVineStore {
  19  	return &GrapeVineStore{D: db}
  20  }
  21  
  22  // SaveScoreSet persists a complete score set for an observer.
  23  // setData is the JSON-marshaled full score set.
  24  // entries maps target pubkey hex to JSON-marshaled individual score entries.
  25  // Uses WriteBatch to avoid exceeding Badger's transaction size limit on large graphs.
  26  func (g *GrapeVineStore) SaveScoreSet(observerHex string, setData []byte, entries map[string][]byte) error {
  27  	if err := g.Update(func(txn *badger.Txn) error {
  28  		return txn.Set(g.setKey(observerHex), setData)
  29  	}); err != nil {
  30  		return err
  31  	}
  32  
  33  	wb := g.NewWriteBatch()
  34  	for targetHex, entryData := range entries {
  35  		key := g.entryKey(observerHex, targetHex)
  36  		if err := wb.Set(key, entryData); err != nil {
  37  			wb.Cancel()
  38  			return err
  39  		}
  40  	}
  41  	return wb.Flush()
  42  }
  43  
  44  // GetScoreSet returns the raw JSON for a full score set, or nil if not found.
  45  func (g *GrapeVineStore) GetScoreSet(observerHex string) ([]byte, error) {
  46  	var data []byte
  47  	err := g.View(func(txn *badger.Txn) error {
  48  		item, err := txn.Get(g.setKey(observerHex))
  49  		if err != nil {
  50  			return err
  51  		}
  52  		data, err = item.ValueCopy(nil)
  53  		return err
  54  	})
  55  	if err == badger.ErrKeyNotFound {
  56  		return nil, nil
  57  	}
  58  	if err != nil {
  59  		return nil, err
  60  	}
  61  	return data, nil
  62  }
  63  
  64  // GetScore returns the raw JSON for a single score entry, or nil if not found.
  65  func (g *GrapeVineStore) GetScore(observerHex, targetHex string) ([]byte, error) {
  66  	var data []byte
  67  	err := g.View(func(txn *badger.Txn) error {
  68  		item, err := txn.Get(g.entryKey(observerHex, targetHex))
  69  		if err != nil {
  70  			return err
  71  		}
  72  		data, err = item.ValueCopy(nil)
  73  		return err
  74  	})
  75  	if err == badger.ErrKeyNotFound {
  76  		return nil, nil
  77  	}
  78  	if err != nil {
  79  		return nil, err
  80  	}
  81  	return data, nil
  82  }
  83  
  84  // DeleteScoreSet removes all stored scores for an observer.
  85  // Uses WriteBatch to avoid exceeding Badger's transaction size limit on large graphs.
  86  func (g *GrapeVineStore) DeleteScoreSet(observerHex string) error {
  87  	var keysToDelete [][]byte
  88  	if err := g.View(func(txn *badger.Txn) error {
  89  		keysToDelete = append(keysToDelete, g.setKey(observerHex))
  90  		prefix := g.entryPrefix(observerHex)
  91  		opts := badger.DefaultIteratorOptions
  92  		opts.PrefetchValues = false
  93  		opts.Prefix = prefix
  94  		it := txn.NewIterator(opts)
  95  		defer it.Close()
  96  		for it.Rewind(); it.Valid(); it.Next() {
  97  			keysToDelete = append(keysToDelete, it.Item().KeyCopy(nil))
  98  		}
  99  		return nil
 100  	}); err != nil {
 101  		return err
 102  	}
 103  
 104  	wb := g.NewWriteBatch()
 105  	for _, key := range keysToDelete {
 106  		if err := wb.Delete(key); err != nil {
 107  			wb.Cancel()
 108  			return err
 109  		}
 110  	}
 111  	return wb.Flush()
 112  }
 113  
 114  func (g *GrapeVineStore) setKey(observerHex string) []byte {
 115  	buf := new(bytes.Buffer)
 116  	buf.WriteString("GV_SET_")
 117  	buf.WriteString(observerHex)
 118  	return buf.Bytes()
 119  }
 120  
 121  func (g *GrapeVineStore) entryKey(observerHex, targetHex string) []byte {
 122  	buf := new(bytes.Buffer)
 123  	buf.WriteString("GV_ENT_")
 124  	buf.WriteString(observerHex)
 125  	buf.WriteString("_")
 126  	buf.WriteString(targetHex)
 127  	return buf.Bytes()
 128  }
 129  
 130  func (g *GrapeVineStore) entryPrefix(observerHex string) []byte {
 131  	buf := new(bytes.Buffer)
 132  	buf.WriteString("GV_ENT_")
 133  	buf.WriteString(observerHex)
 134  	buf.WriteString("_")
 135  	return buf.Bytes()
 136  }
 137