paid-acl.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "bytes"
7 "encoding/json"
8 "time"
9
10 "github.com/dgraph-io/badger/v4"
11 )
12
13 // PaidACL provides database operations for paid ACL data.
14 type PaidACL struct {
15 *D
16 }
17
18 // NewPaidACL creates a new PaidACL instance.
19 func NewPaidACL(db *D) *PaidACL {
20 return &PaidACL{D: db}
21 }
22
23 // SaveSubscription saves or updates a subscription.
24 func (p *PaidACL) SaveSubscription(sub *PaidSubscription) error {
25 return p.Update(func(txn *badger.Txn) error {
26 key := p.getSubKey(sub.PubkeyHex)
27 data, err := json.Marshal(sub)
28 if err != nil {
29 return err
30 }
31 return txn.Set(key, data)
32 })
33 }
34
35 // GetSubscription returns the subscription for a pubkey.
36 func (p *PaidACL) GetSubscription(pubkeyHex string) (*PaidSubscription, error) {
37 var sub PaidSubscription
38 err := p.View(func(txn *badger.Txn) error {
39 key := p.getSubKey(pubkeyHex)
40 item, err := txn.Get(key)
41 if err != nil {
42 return err
43 }
44 val, err := item.ValueCopy(nil)
45 if err != nil {
46 return err
47 }
48 return json.Unmarshal(val, &sub)
49 })
50 if err != nil {
51 return nil, err
52 }
53 return &sub, nil
54 }
55
56 // DeleteSubscription removes a subscription.
57 func (p *PaidACL) DeleteSubscription(pubkeyHex string) error {
58 return p.Update(func(txn *badger.Txn) error {
59 return txn.Delete(p.getSubKey(pubkeyHex))
60 })
61 }
62
63 // ListSubscriptions returns all subscriptions.
64 func (p *PaidACL) ListSubscriptions() ([]*PaidSubscription, error) {
65 var subs []*PaidSubscription
66 err := p.View(func(txn *badger.Txn) error {
67 prefix := p.getSubPrefix()
68 it := txn.NewIterator(badger.IteratorOptions{Prefix: prefix})
69 defer it.Close()
70
71 for it.Rewind(); it.Valid(); it.Next() {
72 item := it.Item()
73 val, err := item.ValueCopy(nil)
74 if err != nil {
75 continue
76 }
77 var sub PaidSubscription
78 if err := json.Unmarshal(val, &sub); err != nil {
79 continue
80 }
81 subs = append(subs, &sub)
82 }
83 return nil
84 })
85 return subs, err
86 }
87
88 // ClaimAlias atomically claims an alias for a pubkey.
89 // Returns an error if the alias is already taken by another pubkey.
90 func (p *PaidACL) ClaimAlias(alias, pubkeyHex string) error {
91 return p.Update(func(txn *badger.Txn) error {
92 // Check if alias is already taken
93 aliasKey := p.getAliasKey(alias)
94 item, err := txn.Get(aliasKey)
95 if err == nil {
96 // Alias exists — check if it's the same pubkey (re-claim is ok)
97 val, err := item.ValueCopy(nil)
98 if err != nil {
99 return err
100 }
101 var existing AliasClaim
102 if err := json.Unmarshal(val, &existing); err != nil {
103 return err
104 }
105 if existing.PubkeyHex == pubkeyHex {
106 return nil // already claimed by this pubkey
107 }
108 return ErrAliasTaken
109 }
110 if err != badger.ErrKeyNotFound {
111 return err
112 }
113
114 // Remove old alias for this pubkey if any
115 revKey := p.getAliasRevKey(pubkeyHex)
116 if item, err := txn.Get(revKey); err == nil {
117 val, _ := item.ValueCopy(nil)
118 if len(val) > 0 {
119 // Delete old forward mapping
120 txn.Delete(p.getAliasKey(string(val)))
121 }
122 }
123
124 // Write forward mapping: alias → pubkey
125 claim := AliasClaim{
126 Alias: alias,
127 PubkeyHex: pubkeyHex,
128 ClaimedAt: time.Now(),
129 }
130 data, err := json.Marshal(claim)
131 if err != nil {
132 return err
133 }
134 if err := txn.Set(aliasKey, data); err != nil {
135 return err
136 }
137
138 // Write reverse mapping: pubkey → alias
139 return txn.Set(revKey, []byte(alias))
140 })
141 }
142
143 // GetAliasByPubkey returns the alias for a pubkey, or "" if none.
144 func (p *PaidACL) GetAliasByPubkey(pubkeyHex string) (string, error) {
145 var alias string
146 err := p.View(func(txn *badger.Txn) error {
147 key := p.getAliasRevKey(pubkeyHex)
148 item, err := txn.Get(key)
149 if err == badger.ErrKeyNotFound {
150 return nil
151 }
152 if err != nil {
153 return err
154 }
155 val, err := item.ValueCopy(nil)
156 if err != nil {
157 return err
158 }
159 alias = string(val)
160 return nil
161 })
162 return alias, err
163 }
164
165 // GetPubkeyByAlias returns the pubkey for an alias, or "" if not found.
166 func (p *PaidACL) GetPubkeyByAlias(alias string) (string, error) {
167 var pubkey string
168 err := p.View(func(txn *badger.Txn) error {
169 key := p.getAliasKey(alias)
170 item, err := txn.Get(key)
171 if err == badger.ErrKeyNotFound {
172 return nil
173 }
174 if err != nil {
175 return err
176 }
177 val, err := item.ValueCopy(nil)
178 if err != nil {
179 return err
180 }
181 var claim AliasClaim
182 if err := json.Unmarshal(val, &claim); err != nil {
183 return err
184 }
185 pubkey = claim.PubkeyHex
186 return nil
187 })
188 return pubkey, err
189 }
190
191 // IsAliasTaken returns true if the alias is claimed by any pubkey.
192 func (p *PaidACL) IsAliasTaken(alias string) (bool, error) {
193 var taken bool
194 err := p.View(func(txn *badger.Txn) error {
195 key := p.getAliasKey(alias)
196 _, err := txn.Get(key)
197 if err == badger.ErrKeyNotFound {
198 return nil
199 }
200 if err != nil {
201 return err
202 }
203 taken = true
204 return nil
205 })
206 return taken, err
207 }
208
209 // Key generation methods
210
211 func (p *PaidACL) getSubKey(pubkeyHex string) []byte {
212 buf := new(bytes.Buffer)
213 buf.WriteString("PAID_SUB_")
214 buf.WriteString(pubkeyHex)
215 return buf.Bytes()
216 }
217
218 func (p *PaidACL) getSubPrefix() []byte {
219 return []byte("PAID_SUB_")
220 }
221
222 func (p *PaidACL) getAliasKey(alias string) []byte {
223 buf := new(bytes.Buffer)
224 buf.WriteString("PAID_ALIAS_")
225 buf.WriteString(alias)
226 return buf.Bytes()
227 }
228
229 func (p *PaidACL) getAliasRevKey(pubkeyHex string) []byte {
230 buf := new(bytes.Buffer)
231 buf.WriteString("PAID_ALIAS_REV_")
232 buf.WriteString(pubkeyHex)
233 return buf.Bytes()
234 }
235
236