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