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