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.
278 lines
7.1 KiB
278 lines
7.1 KiB
package directory |
|
|
|
import ( |
|
"encoding/json" |
|
|
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/errorf" |
|
"next.orly.dev/pkg/encoders/event" |
|
"next.orly.dev/pkg/encoders/tag" |
|
) |
|
|
|
// ReplicationRequestContent represents the JSON content of a Directory Event |
|
// Replication Request event (Kind 39104). |
|
type ReplicationRequestContent struct { |
|
Events []*event.E `json:"events"` |
|
} |
|
|
|
// DirectoryEventReplicationRequest represents a complete Directory Event |
|
// Replication Request event (Kind 39104) with typed access to its components. |
|
type DirectoryEventReplicationRequest struct { |
|
Event *event.E |
|
Content *ReplicationRequestContent |
|
RequestID string |
|
TargetRelay string |
|
} |
|
|
|
// NewDirectoryEventReplicationRequest creates a new Directory Event Replication |
|
// Request event. |
|
func NewDirectoryEventReplicationRequest( |
|
pubkey []byte, |
|
requestID, targetRelay string, |
|
events []*event.E, |
|
) (derr *DirectoryEventReplicationRequest, err error) { |
|
|
|
// Validate required fields |
|
if len(pubkey) != 32 { |
|
return nil, errorf.E("pubkey must be 32 bytes") |
|
} |
|
if requestID == "" { |
|
return nil, errorf.E("request ID is required") |
|
} |
|
if targetRelay == "" { |
|
return nil, errorf.E("target relay is required") |
|
} |
|
if len(events) == 0 { |
|
return nil, errorf.E("at least one event is required") |
|
} |
|
|
|
// Validate all events |
|
for i, ev := range events { |
|
if ev == nil { |
|
return nil, errorf.E("event %d cannot be nil", i) |
|
} |
|
// Verify event signature |
|
if _, err = ev.Verify(); chk.E(err) { |
|
return nil, errorf.E("invalid signature for event %d: %w", i, err) |
|
} |
|
} |
|
|
|
// Create content |
|
content := &ReplicationRequestContent{ |
|
Events: events, |
|
} |
|
|
|
// Marshal content to JSON |
|
var contentBytes []byte |
|
if contentBytes, err = json.Marshal(content); chk.E(err) { |
|
return |
|
} |
|
|
|
// Create base event |
|
ev := CreateBaseEvent(pubkey, DirectoryEventReplicationRequestKind) |
|
ev.Content = contentBytes |
|
|
|
// Add required tags |
|
ev.Tags.Append(tag.NewFromAny(string(RequestIDTag), requestID)) |
|
ev.Tags.Append(tag.NewFromAny(string(RelayTag), targetRelay)) |
|
|
|
derr = &DirectoryEventReplicationRequest{ |
|
Event: ev, |
|
Content: content, |
|
RequestID: requestID, |
|
TargetRelay: targetRelay, |
|
} |
|
|
|
return |
|
} |
|
|
|
// ParseDirectoryEventReplicationRequest parses an event into a |
|
// DirectoryEventReplicationRequest structure with validation. |
|
func ParseDirectoryEventReplicationRequest(ev *event.E) (derr *DirectoryEventReplicationRequest, err error) { |
|
if ev == nil { |
|
return nil, errorf.E("event cannot be nil") |
|
} |
|
|
|
// Validate event kind |
|
if ev.Kind != DirectoryEventReplicationRequestKind.K { |
|
return nil, errorf.E("invalid event kind: expected %d, got %d", |
|
DirectoryEventReplicationRequestKind.K, ev.Kind) |
|
} |
|
|
|
// Parse content |
|
var content ReplicationRequestContent |
|
if len(ev.Content) > 0 { |
|
if err = json.Unmarshal(ev.Content, &content); chk.E(err) { |
|
return nil, errorf.E("failed to parse content: %w", err) |
|
} |
|
} |
|
|
|
// Extract required tags |
|
requestIDTag := ev.Tags.GetFirst(RequestIDTag) |
|
if requestIDTag == nil { |
|
return nil, errorf.E("missing request_id tag") |
|
} |
|
|
|
relayTag := ev.Tags.GetFirst(RelayTag) |
|
if relayTag == nil { |
|
return nil, errorf.E("missing relay tag") |
|
} |
|
|
|
derr = &DirectoryEventReplicationRequest{ |
|
Event: ev, |
|
Content: &content, |
|
RequestID: string(requestIDTag.Value()), |
|
TargetRelay: string(relayTag.Value()), |
|
} |
|
|
|
return |
|
} |
|
|
|
// Validate performs comprehensive validation of a DirectoryEventReplicationRequest. |
|
func (derr *DirectoryEventReplicationRequest) Validate() (err error) { |
|
if derr == nil { |
|
return errorf.E("DirectoryEventReplicationRequest cannot be nil") |
|
} |
|
|
|
if derr.Event == nil { |
|
return errorf.E("event cannot be nil") |
|
} |
|
|
|
// Validate event signature |
|
if _, err = derr.Event.Verify(); chk.E(err) { |
|
return errorf.E("invalid event signature: %w", err) |
|
} |
|
|
|
// Validate required fields |
|
if derr.RequestID == "" { |
|
return errorf.E("request ID is required") |
|
} |
|
|
|
if derr.TargetRelay == "" { |
|
return errorf.E("target relay is required") |
|
} |
|
|
|
if derr.Content == nil { |
|
return errorf.E("content cannot be nil") |
|
} |
|
|
|
if len(derr.Content.Events) == 0 { |
|
return errorf.E("at least one event is required") |
|
} |
|
|
|
// Validate all events in the request |
|
for i, ev := range derr.Content.Events { |
|
if ev == nil { |
|
return errorf.E("event %d cannot be nil", i) |
|
} |
|
// Verify event signature |
|
if _, err = ev.Verify(); chk.E(err) { |
|
return errorf.E("invalid signature for event %d: %w", i, err) |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// GetRequestID returns the unique request identifier. |
|
func (derr *DirectoryEventReplicationRequest) GetRequestID() string { |
|
return derr.RequestID |
|
} |
|
|
|
// GetTargetRelay returns the target relay URL. |
|
func (derr *DirectoryEventReplicationRequest) GetTargetRelay() string { |
|
return derr.TargetRelay |
|
} |
|
|
|
// GetEvents returns the list of events to replicate. |
|
func (derr *DirectoryEventReplicationRequest) GetEvents() []*event.E { |
|
if derr.Content == nil { |
|
return nil |
|
} |
|
return derr.Content.Events |
|
} |
|
|
|
// GetEventCount returns the number of events in the request. |
|
func (derr *DirectoryEventReplicationRequest) GetEventCount() int { |
|
if derr.Content == nil { |
|
return 0 |
|
} |
|
return len(derr.Content.Events) |
|
} |
|
|
|
// HasEvents returns true if the request contains events. |
|
func (derr *DirectoryEventReplicationRequest) HasEvents() bool { |
|
return derr.GetEventCount() > 0 |
|
} |
|
|
|
// GetEventByIndex returns the event at the specified index, or nil if out of bounds. |
|
func (derr *DirectoryEventReplicationRequest) GetEventByIndex(index int) *event.E { |
|
events := derr.GetEvents() |
|
if index < 0 || index >= len(events) { |
|
return nil |
|
} |
|
return events[index] |
|
} |
|
|
|
// ContainsEventKind returns true if the request contains events of the specified kind. |
|
func (derr *DirectoryEventReplicationRequest) ContainsEventKind(kind uint16) bool { |
|
for _, ev := range derr.GetEvents() { |
|
if ev.Kind == kind { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// GetEventsByKind returns all events of the specified kind. |
|
func (derr *DirectoryEventReplicationRequest) GetEventsByKind(kind uint16) []*event.E { |
|
var result []*event.E |
|
for _, ev := range derr.GetEvents() { |
|
if ev.Kind == kind { |
|
result = append(result, ev) |
|
} |
|
} |
|
return result |
|
} |
|
|
|
// GetDirectoryEvents returns only the directory events from the request. |
|
func (derr *DirectoryEventReplicationRequest) GetDirectoryEvents() []*event.E { |
|
var result []*event.E |
|
for _, ev := range derr.GetEvents() { |
|
if IsDirectoryEventKind(ev.Kind) { |
|
result = append(result, ev) |
|
} |
|
} |
|
return result |
|
} |
|
|
|
// GetNonDirectoryEvents returns only the non-directory events from the request. |
|
func (derr *DirectoryEventReplicationRequest) GetNonDirectoryEvents() []*event.E { |
|
var result []*event.E |
|
for _, ev := range derr.GetEvents() { |
|
if !IsDirectoryEventKind(ev.Kind) { |
|
result = append(result, ev) |
|
} |
|
} |
|
return result |
|
} |
|
|
|
// GetEventsByAuthor returns all events from the specified author. |
|
func (derr *DirectoryEventReplicationRequest) GetEventsByAuthor(pubkey []byte) []*event.E { |
|
var result []*event.E |
|
for _, ev := range derr.GetEvents() { |
|
if len(ev.Pubkey) == len(pubkey) { |
|
match := true |
|
for i := range pubkey { |
|
if ev.Pubkey[i] != pubkey[i] { |
|
match = false |
|
break |
|
} |
|
} |
|
if match { |
|
result = append(result, ev) |
|
} |
|
} |
|
} |
|
return result |
|
}
|
|
|