260 lines
6.0 KiB
Go
260 lines
6.0 KiB
Go
package database
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/dgraph-io/badger/v4"
|
|
"lol.mleku.dev/chk"
|
|
"lol.mleku.dev/log"
|
|
"next.orly.dev/pkg/encoders/hex"
|
|
)
|
|
|
|
// NIP43Membership represents membership metadata for NIP-43
|
|
type NIP43Membership struct {
|
|
Pubkey []byte
|
|
AddedAt time.Time
|
|
InviteCode string
|
|
}
|
|
|
|
// Database key prefixes for NIP-43
|
|
const (
|
|
nip43MemberPrefix = "nip43:member:"
|
|
nip43InvitePrefix = "nip43:invite:"
|
|
)
|
|
|
|
// AddNIP43Member adds a member to the NIP-43 membership list
|
|
func (d *D) AddNIP43Member(pubkey []byte, inviteCode string) error {
|
|
if len(pubkey) != 32 {
|
|
return fmt.Errorf("invalid pubkey length: %d", len(pubkey))
|
|
}
|
|
|
|
key := append([]byte(nip43MemberPrefix), pubkey...)
|
|
|
|
// Create membership record
|
|
membership := NIP43Membership{
|
|
Pubkey: pubkey,
|
|
AddedAt: time.Now(),
|
|
InviteCode: inviteCode,
|
|
}
|
|
|
|
// Serialize membership data
|
|
val := serializeNIP43Membership(membership)
|
|
|
|
return d.DB.Update(func(txn *badger.Txn) error {
|
|
return txn.Set(key, val)
|
|
})
|
|
}
|
|
|
|
// RemoveNIP43Member removes a member from the NIP-43 membership list
|
|
func (d *D) RemoveNIP43Member(pubkey []byte) error {
|
|
if len(pubkey) != 32 {
|
|
return fmt.Errorf("invalid pubkey length: %d", len(pubkey))
|
|
}
|
|
|
|
key := append([]byte(nip43MemberPrefix), pubkey...)
|
|
|
|
return d.DB.Update(func(txn *badger.Txn) error {
|
|
return txn.Delete(key)
|
|
})
|
|
}
|
|
|
|
// IsNIP43Member checks if a pubkey is a NIP-43 member
|
|
func (d *D) IsNIP43Member(pubkey []byte) (isMember bool, err error) {
|
|
if len(pubkey) != 32 {
|
|
return false, fmt.Errorf("invalid pubkey length: %d", len(pubkey))
|
|
}
|
|
|
|
key := append([]byte(nip43MemberPrefix), pubkey...)
|
|
|
|
err = d.DB.View(func(txn *badger.Txn) error {
|
|
_, err := txn.Get(key)
|
|
if err == badger.ErrKeyNotFound {
|
|
isMember = false
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
isMember = true
|
|
return nil
|
|
})
|
|
|
|
return isMember, err
|
|
}
|
|
|
|
// GetNIP43Membership retrieves membership details for a pubkey
|
|
func (d *D) GetNIP43Membership(pubkey []byte) (*NIP43Membership, error) {
|
|
if len(pubkey) != 32 {
|
|
return nil, fmt.Errorf("invalid pubkey length: %d", len(pubkey))
|
|
}
|
|
|
|
key := append([]byte(nip43MemberPrefix), pubkey...)
|
|
var membership *NIP43Membership
|
|
|
|
err := d.DB.View(func(txn *badger.Txn) error {
|
|
item, err := txn.Get(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return item.Value(func(val []byte) error {
|
|
membership = deserializeNIP43Membership(val)
|
|
return nil
|
|
})
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return membership, nil
|
|
}
|
|
|
|
// GetAllNIP43Members returns all NIP-43 members
|
|
func (d *D) GetAllNIP43Members() ([][]byte, error) {
|
|
var members [][]byte
|
|
prefix := []byte(nip43MemberPrefix)
|
|
|
|
err := d.DB.View(func(txn *badger.Txn) error {
|
|
opts := badger.DefaultIteratorOptions
|
|
opts.Prefix = prefix
|
|
opts.PrefetchValues = false // We only need keys
|
|
|
|
it := txn.NewIterator(opts)
|
|
defer it.Close()
|
|
|
|
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
|
item := it.Item()
|
|
key := item.Key()
|
|
// Extract pubkey from key (skip prefix)
|
|
pubkey := make([]byte, 32)
|
|
copy(pubkey, key[len(prefix):])
|
|
members = append(members, pubkey)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return members, err
|
|
}
|
|
|
|
// StoreInviteCode stores an invite code with expiry
|
|
func (d *D) StoreInviteCode(code string, expiresAt time.Time) error {
|
|
key := append([]byte(nip43InvitePrefix), []byte(code)...)
|
|
|
|
// Serialize expiry time as unix timestamp
|
|
val := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(val, uint64(expiresAt.Unix()))
|
|
|
|
return d.DB.Update(func(txn *badger.Txn) error {
|
|
entry := badger.NewEntry(key, val).WithTTL(time.Until(expiresAt))
|
|
return txn.SetEntry(entry)
|
|
})
|
|
}
|
|
|
|
// ValidateInviteCode checks if an invite code is valid and not expired
|
|
func (d *D) ValidateInviteCode(code string) (valid bool, err error) {
|
|
key := append([]byte(nip43InvitePrefix), []byte(code)...)
|
|
|
|
err = d.DB.View(func(txn *badger.Txn) error {
|
|
item, err := txn.Get(key)
|
|
if err == badger.ErrKeyNotFound {
|
|
valid = false
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return item.Value(func(val []byte) error {
|
|
if len(val) != 8 {
|
|
return fmt.Errorf("invalid invite code value")
|
|
}
|
|
expiresAt := int64(binary.BigEndian.Uint64(val))
|
|
valid = time.Now().Unix() < expiresAt
|
|
return nil
|
|
})
|
|
})
|
|
|
|
return valid, err
|
|
}
|
|
|
|
// DeleteInviteCode removes an invite code (after use)
|
|
func (d *D) DeleteInviteCode(code string) error {
|
|
key := append([]byte(nip43InvitePrefix), []byte(code)...)
|
|
|
|
return d.DB.Update(func(txn *badger.Txn) error {
|
|
return txn.Delete(key)
|
|
})
|
|
}
|
|
|
|
// Helper functions for serialization
|
|
|
|
func serializeNIP43Membership(m NIP43Membership) []byte {
|
|
// Format: [pubkey(32)] [timestamp(8)] [invite_code_len(2)] [invite_code]
|
|
codeBytes := []byte(m.InviteCode)
|
|
codeLen := len(codeBytes)
|
|
|
|
buf := make([]byte, 32+8+2+codeLen)
|
|
|
|
// Copy pubkey
|
|
copy(buf[0:32], m.Pubkey)
|
|
|
|
// Write timestamp
|
|
binary.BigEndian.PutUint64(buf[32:40], uint64(m.AddedAt.Unix()))
|
|
|
|
// Write invite code length
|
|
binary.BigEndian.PutUint16(buf[40:42], uint16(codeLen))
|
|
|
|
// Write invite code
|
|
copy(buf[42:], codeBytes)
|
|
|
|
return buf
|
|
}
|
|
|
|
func deserializeNIP43Membership(data []byte) *NIP43Membership {
|
|
if len(data) < 42 {
|
|
return nil
|
|
}
|
|
|
|
m := &NIP43Membership{}
|
|
|
|
// Read pubkey
|
|
m.Pubkey = make([]byte, 32)
|
|
copy(m.Pubkey, data[0:32])
|
|
|
|
// Read timestamp
|
|
timestamp := binary.BigEndian.Uint64(data[32:40])
|
|
m.AddedAt = time.Unix(int64(timestamp), 0)
|
|
|
|
// Read invite code
|
|
codeLen := binary.BigEndian.Uint16(data[40:42])
|
|
if len(data) >= 42+int(codeLen) {
|
|
m.InviteCode = string(data[42 : 42+codeLen])
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// PublishNIP43MembershipEvent publishes membership change events
|
|
func (d *D) PublishNIP43MembershipEvent(kind int, pubkey []byte) error {
|
|
log.I.F("publishing NIP-43 event kind %d for pubkey %s", kind, hex.Enc(pubkey))
|
|
|
|
// Get relay identity
|
|
relaySecret, err := d.GetOrCreateRelayIdentitySecret()
|
|
if chk.E(err) {
|
|
return err
|
|
}
|
|
|
|
// This would integrate with the event publisher
|
|
// For now, just log it
|
|
log.D.F("would publish kind %d event for member %s", kind, hex.Enc(pubkey))
|
|
|
|
// The actual publishing will be done by the handler
|
|
_ = relaySecret
|
|
|
|
return nil
|
|
}
|