Add relay identity management and subscription enhancements.
Some checks failed
Go / build (push) Has been cancelled
Some checks failed
Go / build (push) Has been cancelled
- Introduced relay identity management for subscriptions and follow-list sync. - Added `IdentityRequested` function to handle the `identity` subcommand. - Implemented periodic follow-list synchronization for active subscribers. - Enhanced payment handling to include payer pubkey and subscription updates. - Added trial expiry and subscription expiry notifications.
This commit is contained in:
@@ -66,3 +66,15 @@ func (s *S) Type() (typ string) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddFollow forwards a pubkey to the active ACL if it supports dynamic follows
|
||||
func (s *S) AddFollow(pub []byte) {
|
||||
for _, i := range s.ACL {
|
||||
if i.Type() == s.Active.Load() {
|
||||
if f, ok := i.(*Follows); ok {
|
||||
f.AddFollow(pub)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -370,6 +371,32 @@ func (f *Follows) GetFollowedPubkeys() [][]byte {
|
||||
return followedPubkeys
|
||||
}
|
||||
|
||||
// AddFollow appends a pubkey to the in-memory follows list if not already present
|
||||
// and signals the syncer to refresh subscriptions.
|
||||
func (f *Follows) AddFollow(pub []byte) {
|
||||
if len(pub) == 0 {
|
||||
return
|
||||
}
|
||||
f.followsMx.Lock()
|
||||
defer f.followsMx.Unlock()
|
||||
for _, p := range f.follows {
|
||||
if bytes.Equal(p, pub) {
|
||||
return
|
||||
}
|
||||
}
|
||||
b := make([]byte, len(pub))
|
||||
copy(b, pub)
|
||||
f.follows = append(f.follows, b)
|
||||
// notify syncer if initialized
|
||||
if f.updated != nil {
|
||||
select {
|
||||
case f.updated <- struct{}{}:
|
||||
default:
|
||||
// if channel is full or not yet listened to, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.T.F("registering follows ACL")
|
||||
Registry.Register(new(Follows))
|
||||
|
||||
81
pkg/database/identity.go
Normal file
81
pkg/database/identity.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/crypto/keys"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
)
|
||||
|
||||
const relayIdentitySecretKey = "relay:identity:sk"
|
||||
|
||||
// GetRelayIdentitySecret returns the relay identity secret key bytes if present.
|
||||
// If the key is not found, returns (nil, badger.ErrKeyNotFound).
|
||||
func (d *D) GetRelayIdentitySecret() (skb []byte, err error) {
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get([]byte(relayIdentitySecretKey))
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return item.Value(func(val []byte) error {
|
||||
// value stored as hex string
|
||||
b, err := hex.Dec(string(val))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
skb = make([]byte, len(b))
|
||||
copy(skb, b)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// SetRelayIdentitySecret stores the relay identity secret key bytes (expects 32 bytes).
|
||||
func (d *D) SetRelayIdentitySecret(skb []byte) (err error) {
|
||||
if len(skb) != 32 {
|
||||
return fmt.Errorf("invalid secret key length: %d", len(skb))
|
||||
}
|
||||
val := []byte(hex.Enc(skb))
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set([]byte(relayIdentitySecretKey), val)
|
||||
})
|
||||
}
|
||||
|
||||
// GetOrCreateRelayIdentitySecret retrieves the existing relay identity secret
|
||||
// key or creates and stores a new one if none exists.
|
||||
func (d *D) GetOrCreateRelayIdentitySecret() (skb []byte, err error) {
|
||||
// Try get fast path
|
||||
if skb, err = d.GetRelayIdentitySecret(); err == nil && len(skb) == 32 {
|
||||
return skb, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create new key and store atomically
|
||||
var gen []byte
|
||||
if gen, err = keys.GenerateSecretKey(); chk.E(err) {
|
||||
return nil, err
|
||||
}
|
||||
if err = d.SetRelayIdentitySecret(gen); chk.E(err) {
|
||||
return nil, err
|
||||
}
|
||||
log.I.F("generated new relay identity key (pub=%s)", mustPub(gen))
|
||||
return gen, nil
|
||||
}
|
||||
|
||||
func mustPub(skb []byte) string {
|
||||
pk, err := keys.SecretBytesToPubKeyHex(skb)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return pk
|
||||
}
|
||||
@@ -188,3 +188,30 @@ func (d *D) GetPaymentHistory(pubkey []byte) ([]Payment, error) {
|
||||
|
||||
return payments, err
|
||||
}
|
||||
|
||||
// IsFirstTimeUser checks if a user is logging in for the first time and marks them as seen
|
||||
func (d *D) IsFirstTimeUser(pubkey []byte) (bool, error) {
|
||||
key := fmt.Sprintf("firstlogin:%s", hex.EncodeToString(pubkey))
|
||||
|
||||
isFirstTime := false
|
||||
err := d.DB.Update(
|
||||
func(txn *badger.Txn) error {
|
||||
_, err := txn.Get([]byte(key))
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
// First time - record the login
|
||||
isFirstTime = true
|
||||
now := time.Now()
|
||||
data, err := json.Marshal(map[string]interface{}{
|
||||
"first_login": now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set([]byte(key), data)
|
||||
}
|
||||
return err // Return any other error as-is
|
||||
},
|
||||
)
|
||||
|
||||
return isFirstTime, err
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
v0.7.1
|
||||
v0.8.0
|
||||
Reference in New Issue
Block a user