subscriptions.go raw

   1  package neo4j
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"time"
   7  
   8  	"next.orly.dev/pkg/database"
   9  	"next.orly.dev/pkg/nostr/encoders/hex"
  10  )
  11  
  12  // Subscription and payment methods
  13  // Simplified implementation using marker-based storage via Badger
  14  // For production graph-based storage, these could use Neo4j nodes with relationships
  15  
  16  // GetSubscription retrieves subscription information for a pubkey
  17  func (n *N) GetSubscription(pubkey []byte) (*database.Subscription, error) {
  18  	key := "sub_" + hex.Enc(pubkey)
  19  	data, err := n.GetMarker(key)
  20  	if err != nil {
  21  		return nil, err
  22  	}
  23  
  24  	var sub database.Subscription
  25  	if err := json.Unmarshal(data, &sub); err != nil {
  26  		return nil, fmt.Errorf("failed to unmarshal subscription: %w", err)
  27  	}
  28  
  29  	return &sub, nil
  30  }
  31  
  32  // IsSubscriptionActive checks if a pubkey has an active subscription
  33  func (n *N) IsSubscriptionActive(pubkey []byte) (bool, error) {
  34  	sub, err := n.GetSubscription(pubkey)
  35  	if err != nil {
  36  		return false, nil // No subscription = not active
  37  	}
  38  
  39  	return sub.PaidUntil.After(time.Now()), nil
  40  }
  41  
  42  // ExtendSubscription extends a subscription by the specified number of days
  43  func (n *N) ExtendSubscription(pubkey []byte, days int) error {
  44  	key := "sub_" + hex.Enc(pubkey)
  45  
  46  	// Get existing subscription or create new
  47  	var sub database.Subscription
  48  	data, err := n.GetMarker(key)
  49  	if err == nil {
  50  		if err := json.Unmarshal(data, &sub); err != nil {
  51  			return fmt.Errorf("failed to unmarshal subscription: %w", err)
  52  		}
  53  	} else {
  54  		// New subscription - set trial period
  55  		sub.TrialEnd = time.Now()
  56  		sub.PaidUntil = time.Now()
  57  	}
  58  
  59  	// Extend expiration
  60  	if sub.PaidUntil.Before(time.Now()) {
  61  		sub.PaidUntil = time.Now()
  62  	}
  63  	sub.PaidUntil = sub.PaidUntil.Add(time.Duration(days) * 24 * time.Hour)
  64  
  65  	// Save
  66  	data, err = json.Marshal(sub)
  67  	if err != nil {
  68  		return fmt.Errorf("failed to marshal subscription: %w", err)
  69  	}
  70  
  71  	return n.SetMarker(key, data)
  72  }
  73  
  74  // RecordPayment records a payment for subscription extension
  75  func (n *N) RecordPayment(
  76  	pubkey []byte, amount int64, invoice, preimage string,
  77  ) error {
  78  	// Store payment in payments list
  79  	key := "payments_" + hex.Enc(pubkey)
  80  
  81  	var payments []database.Payment
  82  	data, err := n.GetMarker(key)
  83  	if err == nil {
  84  		if err := json.Unmarshal(data, &payments); err != nil {
  85  			return fmt.Errorf("failed to unmarshal payments: %w", err)
  86  		}
  87  	}
  88  
  89  	payment := database.Payment{
  90  		Amount:    amount,
  91  		Timestamp: time.Now(),
  92  		Invoice:   invoice,
  93  		Preimage:  preimage,
  94  	}
  95  
  96  	payments = append(payments, payment)
  97  
  98  	data, err = json.Marshal(payments)
  99  	if err != nil {
 100  		return fmt.Errorf("failed to marshal payments: %w", err)
 101  	}
 102  
 103  	return n.SetMarker(key, data)
 104  }
 105  
 106  // GetPaymentHistory retrieves payment history for a pubkey
 107  func (n *N) GetPaymentHistory(pubkey []byte) ([]database.Payment, error) {
 108  	key := "payments_" + hex.Enc(pubkey)
 109  
 110  	data, err := n.GetMarker(key)
 111  	if err != nil {
 112  		return nil, nil // No payments = empty list
 113  	}
 114  
 115  	var payments []database.Payment
 116  	if err := json.Unmarshal(data, &payments); err != nil {
 117  		return nil, fmt.Errorf("failed to unmarshal payments: %w", err)
 118  	}
 119  
 120  	return payments, nil
 121  }
 122  
 123  // ExtendBlossomSubscription extends a Blossom storage subscription
 124  func (n *N) ExtendBlossomSubscription(
 125  	pubkey []byte, tier string, storageMB int64, daysExtended int,
 126  ) error {
 127  	key := "blossom_" + hex.Enc(pubkey)
 128  
 129  	// Simple implementation - just store tier and expiry
 130  	data := map[string]interface{}{
 131  		"tier":      tier,
 132  		"storageMB": storageMB,
 133  		"extended":  daysExtended,
 134  		"updated":   time.Now(),
 135  	}
 136  
 137  	jsonData, err := json.Marshal(data)
 138  	if err != nil {
 139  		return fmt.Errorf("failed to marshal blossom subscription: %w", err)
 140  	}
 141  
 142  	return n.SetMarker(key, jsonData)
 143  }
 144  
 145  // GetBlossomStorageQuota retrieves the storage quota for a pubkey
 146  func (n *N) GetBlossomStorageQuota(pubkey []byte) (quotaMB int64, err error) {
 147  	key := "blossom_" + hex.Enc(pubkey)
 148  
 149  	data, err := n.GetMarker(key)
 150  	if err != nil {
 151  		return 0, nil // No subscription = 0 quota
 152  	}
 153  
 154  	var subData map[string]interface{}
 155  	if err := json.Unmarshal(data, &subData); err != nil {
 156  		return 0, fmt.Errorf("failed to unmarshal blossom data: %w", err)
 157  	}
 158  
 159  	if storageMB, ok := subData["storageMB"].(float64); ok {
 160  		return int64(storageMB), nil
 161  	}
 162  
 163  	return 0, nil
 164  }
 165  
 166  // IsFirstTimeUser checks if this is the first time a user is accessing the relay
 167  func (n *N) IsFirstTimeUser(pubkey []byte) (bool, error) {
 168  	key := "first_seen_" + hex.Enc(pubkey)
 169  
 170  	// If marker exists, not first time
 171  	if n.HasMarker(key) {
 172  		return false, nil
 173  	}
 174  
 175  	// Mark as seen
 176  	if err := n.SetMarker(key, []byte{1}); err != nil {
 177  		return true, err
 178  	}
 179  
 180  	return true, nil
 181  }
 182