You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
6.0 KiB
259 lines
6.0 KiB
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 |
|
}
|
|
|