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