replication_request.go raw

   1  package directory
   2  
   3  import (
   4  	"encoding/json"
   5  
   6  	"next.orly.dev/pkg/lol/chk"
   7  	"next.orly.dev/pkg/lol/errorf"
   8  	"next.orly.dev/pkg/nostr/encoders/event"
   9  	"next.orly.dev/pkg/nostr/encoders/tag"
  10  )
  11  
  12  // ReplicationRequestContent represents the JSON content of a Directory Event
  13  // Replication Request event (Kind 39104).
  14  type ReplicationRequestContent struct {
  15  	Events []*event.E `json:"events"`
  16  }
  17  
  18  // DirectoryEventReplicationRequest represents a complete Directory Event
  19  // Replication Request event (Kind 39104) with typed access to its components.
  20  type DirectoryEventReplicationRequest struct {
  21  	Event       *event.E
  22  	Content     *ReplicationRequestContent
  23  	RequestID   string
  24  	TargetRelay string
  25  }
  26  
  27  // NewDirectoryEventReplicationRequest creates a new Directory Event Replication
  28  // Request event.
  29  func NewDirectoryEventReplicationRequest(
  30  	pubkey []byte,
  31  	requestID, targetRelay string,
  32  	events []*event.E,
  33  ) (derr *DirectoryEventReplicationRequest, err error) {
  34  
  35  	// Validate required fields
  36  	if len(pubkey) != 32 {
  37  		return nil, errorf.E("pubkey must be 32 bytes")
  38  	}
  39  	if requestID == "" {
  40  		return nil, errorf.E("request ID is required")
  41  	}
  42  	if targetRelay == "" {
  43  		return nil, errorf.E("target relay is required")
  44  	}
  45  	if len(events) == 0 {
  46  		return nil, errorf.E("at least one event is required")
  47  	}
  48  
  49  	// Validate all events
  50  	for i, ev := range events {
  51  		if ev == nil {
  52  			return nil, errorf.E("event %d cannot be nil", i)
  53  		}
  54  		// Verify event signature
  55  		if _, err = ev.Verify(); chk.E(err) {
  56  			return nil, errorf.E("invalid signature for event %d: %w", i, err)
  57  		}
  58  	}
  59  
  60  	// Create content
  61  	content := &ReplicationRequestContent{
  62  		Events: events,
  63  	}
  64  
  65  	// Marshal content to JSON
  66  	var contentBytes []byte
  67  	if contentBytes, err = json.Marshal(content); chk.E(err) {
  68  		return
  69  	}
  70  
  71  	// Create base event
  72  	ev := CreateBaseEvent(pubkey, DirectoryEventReplicationRequestKind)
  73  	ev.Content = contentBytes
  74  
  75  	// Add required tags
  76  	ev.Tags.Append(tag.NewFromAny(string(RequestIDTag), requestID))
  77  	ev.Tags.Append(tag.NewFromAny(string(RelayTag), targetRelay))
  78  
  79  	derr = &DirectoryEventReplicationRequest{
  80  		Event:       ev,
  81  		Content:     content,
  82  		RequestID:   requestID,
  83  		TargetRelay: targetRelay,
  84  	}
  85  
  86  	return
  87  }
  88  
  89  // ParseDirectoryEventReplicationRequest parses an event into a
  90  // DirectoryEventReplicationRequest structure with validation.
  91  func ParseDirectoryEventReplicationRequest(ev *event.E) (derr *DirectoryEventReplicationRequest, err error) {
  92  	if ev == nil {
  93  		return nil, errorf.E("event cannot be nil")
  94  	}
  95  
  96  	// Validate event kind
  97  	if ev.Kind != DirectoryEventReplicationRequestKind.K {
  98  		return nil, errorf.E("invalid event kind: expected %d, got %d",
  99  			DirectoryEventReplicationRequestKind.K, ev.Kind)
 100  	}
 101  
 102  	// Parse content
 103  	var content ReplicationRequestContent
 104  	if len(ev.Content) > 0 {
 105  		if err = json.Unmarshal(ev.Content, &content); chk.E(err) {
 106  			return nil, errorf.E("failed to parse content: %w", err)
 107  		}
 108  	}
 109  
 110  	// Extract required tags
 111  	requestIDTag := ev.Tags.GetFirst(RequestIDTag)
 112  	if requestIDTag == nil {
 113  		return nil, errorf.E("missing request_id tag")
 114  	}
 115  
 116  	relayTag := ev.Tags.GetFirst(RelayTag)
 117  	if relayTag == nil {
 118  		return nil, errorf.E("missing relay tag")
 119  	}
 120  
 121  	derr = &DirectoryEventReplicationRequest{
 122  		Event:       ev,
 123  		Content:     &content,
 124  		RequestID:   string(requestIDTag.Value()),
 125  		TargetRelay: string(relayTag.Value()),
 126  	}
 127  
 128  	return
 129  }
 130  
 131  // Validate performs comprehensive validation of a DirectoryEventReplicationRequest.
 132  func (derr *DirectoryEventReplicationRequest) Validate() (err error) {
 133  	if derr == nil {
 134  		return errorf.E("DirectoryEventReplicationRequest cannot be nil")
 135  	}
 136  
 137  	if derr.Event == nil {
 138  		return errorf.E("event cannot be nil")
 139  	}
 140  
 141  	// Validate event signature
 142  	if _, err = derr.Event.Verify(); chk.E(err) {
 143  		return errorf.E("invalid event signature: %w", err)
 144  	}
 145  
 146  	// Validate required fields
 147  	if derr.RequestID == "" {
 148  		return errorf.E("request ID is required")
 149  	}
 150  
 151  	if derr.TargetRelay == "" {
 152  		return errorf.E("target relay is required")
 153  	}
 154  
 155  	if derr.Content == nil {
 156  		return errorf.E("content cannot be nil")
 157  	}
 158  
 159  	if len(derr.Content.Events) == 0 {
 160  		return errorf.E("at least one event is required")
 161  	}
 162  
 163  	// Validate all events in the request
 164  	for i, ev := range derr.Content.Events {
 165  		if ev == nil {
 166  			return errorf.E("event %d cannot be nil", i)
 167  		}
 168  		// Verify event signature
 169  		if _, err = ev.Verify(); chk.E(err) {
 170  			return errorf.E("invalid signature for event %d: %w", i, err)
 171  		}
 172  	}
 173  
 174  	return nil
 175  }
 176  
 177  // GetRequestID returns the unique request identifier.
 178  func (derr *DirectoryEventReplicationRequest) GetRequestID() string {
 179  	return derr.RequestID
 180  }
 181  
 182  // GetTargetRelay returns the target relay URL.
 183  func (derr *DirectoryEventReplicationRequest) GetTargetRelay() string {
 184  	return derr.TargetRelay
 185  }
 186  
 187  // GetEvents returns the list of events to replicate.
 188  func (derr *DirectoryEventReplicationRequest) GetEvents() []*event.E {
 189  	if derr.Content == nil {
 190  		return nil
 191  	}
 192  	return derr.Content.Events
 193  }
 194  
 195  // GetEventCount returns the number of events in the request.
 196  func (derr *DirectoryEventReplicationRequest) GetEventCount() int {
 197  	if derr.Content == nil {
 198  		return 0
 199  	}
 200  	return len(derr.Content.Events)
 201  }
 202  
 203  // HasEvents returns true if the request contains events.
 204  func (derr *DirectoryEventReplicationRequest) HasEvents() bool {
 205  	return derr.GetEventCount() > 0
 206  }
 207  
 208  // GetEventByIndex returns the event at the specified index, or nil if out of bounds.
 209  func (derr *DirectoryEventReplicationRequest) GetEventByIndex(index int) *event.E {
 210  	events := derr.GetEvents()
 211  	if index < 0 || index >= len(events) {
 212  		return nil
 213  	}
 214  	return events[index]
 215  }
 216  
 217  // ContainsEventKind returns true if the request contains events of the specified kind.
 218  func (derr *DirectoryEventReplicationRequest) ContainsEventKind(kind uint16) bool {
 219  	for _, ev := range derr.GetEvents() {
 220  		if ev.Kind == kind {
 221  			return true
 222  		}
 223  	}
 224  	return false
 225  }
 226  
 227  // GetEventsByKind returns all events of the specified kind.
 228  func (derr *DirectoryEventReplicationRequest) GetEventsByKind(kind uint16) []*event.E {
 229  	var result []*event.E
 230  	for _, ev := range derr.GetEvents() {
 231  		if ev.Kind == kind {
 232  			result = append(result, ev)
 233  		}
 234  	}
 235  	return result
 236  }
 237  
 238  // GetDirectoryEvents returns only the directory events from the request.
 239  func (derr *DirectoryEventReplicationRequest) GetDirectoryEvents() []*event.E {
 240  	var result []*event.E
 241  	for _, ev := range derr.GetEvents() {
 242  		if IsDirectoryEventKind(ev.Kind) {
 243  			result = append(result, ev)
 244  		}
 245  	}
 246  	return result
 247  }
 248  
 249  // GetNonDirectoryEvents returns only the non-directory events from the request.
 250  func (derr *DirectoryEventReplicationRequest) GetNonDirectoryEvents() []*event.E {
 251  	var result []*event.E
 252  	for _, ev := range derr.GetEvents() {
 253  		if !IsDirectoryEventKind(ev.Kind) {
 254  			result = append(result, ev)
 255  		}
 256  	}
 257  	return result
 258  }
 259  
 260  // GetEventsByAuthor returns all events from the specified author.
 261  func (derr *DirectoryEventReplicationRequest) GetEventsByAuthor(pubkey []byte) []*event.E {
 262  	var result []*event.E
 263  	for _, ev := range derr.GetEvents() {
 264  		if len(ev.Pubkey) == len(pubkey) {
 265  			match := true
 266  			for i := range pubkey {
 267  				if ev.Pubkey[i] != pubkey[i] {
 268  					match = false
 269  					break
 270  				}
 271  			}
 272  			if match {
 273  				result = append(result, ev)
 274  			}
 275  		}
 276  	}
 277  	return result
 278  }
 279