import-export.go raw
1 package neo4j
2
3 import (
4 "bufio"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
9
10 "next.orly.dev/pkg/nostr/encoders/event"
11 "next.orly.dev/pkg/nostr/encoders/hex"
12 "next.orly.dev/pkg/nostr/encoders/tag"
13 )
14
15 // Import imports events from a reader (JSONL format)
16 func (n *N) Import(rr io.Reader) {
17 n.ImportEventsFromReader(context.Background(), rr)
18 }
19
20 // Export exports events to a writer (JSONL format)
21 // If pubkeys are provided, only exports events from those authors
22 // Otherwise exports all events
23 func (n *N) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
24 var cypher string
25 params := make(map[string]any)
26
27 if len(pubkeys) > 0 {
28 // Export events for specific pubkeys
29 pubkeyStrings := make([]string, len(pubkeys))
30 for i, pk := range pubkeys {
31 pubkeyStrings[i] = hex.Enc(pk)
32 }
33 params["pubkeys"] = pubkeyStrings
34 cypher = `
35 MATCH (e:Event)
36 WHERE e.pubkey IN $pubkeys
37 RETURN e.id AS id, e.kind AS kind, e.pubkey AS pubkey,
38 e.created_at AS created_at, e.content AS content,
39 e.sig AS sig, e.tags AS tags
40 ORDER BY e.created_at ASC`
41 } else {
42 // Export all events
43 cypher = `
44 MATCH (e:Event)
45 RETURN e.id AS id, e.kind AS kind, e.pubkey AS pubkey,
46 e.created_at AS created_at, e.content AS content,
47 e.sig AS sig, e.tags AS tags
48 ORDER BY e.created_at ASC`
49 }
50
51 result, err := n.ExecuteRead(c, cypher, params)
52 if err != nil {
53 n.Logger.Warningf("failed to query events for export: %v", err)
54 fmt.Fprintf(w, "# Export failed: %v\n", err)
55 return
56 }
57
58 count := 0
59 for result.Next(c) {
60 record := result.Record()
61 if record == nil {
62 continue
63 }
64
65 // Build event from record
66 ev := &event.E{}
67
68 // Parse ID
69 if idRaw, found := record.Get("id"); found {
70 if idStr, ok := idRaw.(string); ok {
71 if idBytes, err := hex.Dec(idStr); err == nil && len(idBytes) == 32 {
72 copy(ev.ID[:], idBytes)
73 }
74 }
75 }
76
77 // Parse kind
78 if kindRaw, found := record.Get("kind"); found {
79 if kindVal, ok := kindRaw.(int64); ok {
80 ev.Kind = uint16(kindVal)
81 }
82 }
83
84 // Parse pubkey
85 if pkRaw, found := record.Get("pubkey"); found {
86 if pkStr, ok := pkRaw.(string); ok {
87 if pkBytes, err := hex.Dec(pkStr); err == nil && len(pkBytes) == 32 {
88 copy(ev.Pubkey[:], pkBytes)
89 }
90 }
91 }
92
93 // Parse created_at
94 if tsRaw, found := record.Get("created_at"); found {
95 if tsVal, ok := tsRaw.(int64); ok {
96 ev.CreatedAt = tsVal
97 }
98 }
99
100 // Parse content
101 if contentRaw, found := record.Get("content"); found {
102 if contentStr, ok := contentRaw.(string); ok {
103 ev.Content = []byte(contentStr)
104 }
105 }
106
107 // Parse sig
108 if sigRaw, found := record.Get("sig"); found {
109 if sigStr, ok := sigRaw.(string); ok {
110 if sigBytes, err := hex.Dec(sigStr); err == nil && len(sigBytes) == 64 {
111 copy(ev.Sig[:], sigBytes)
112 }
113 }
114 }
115
116 // Parse tags (stored as JSON string)
117 if tagsRaw, found := record.Get("tags"); found {
118 if tagsStr, ok := tagsRaw.(string); ok {
119 ev.Tags = &tag.S{}
120 if err := json.Unmarshal([]byte(tagsStr), ev.Tags); err != nil {
121 n.Logger.Warningf("failed to unmarshal tags: %v", err)
122 }
123 }
124 }
125
126 // Write event as JSON line
127 if evJSON, err := json.Marshal(ev); err == nil {
128 fmt.Fprintf(w, "%s\n", evJSON)
129 count++
130 }
131 }
132
133 n.Logger.Infof("exported %d events", count)
134 }
135
136 // ImportEventsFromReader imports events from a reader
137 func (n *N) ImportEventsFromReader(ctx context.Context, rr io.Reader) error {
138 scanner := bufio.NewScanner(rr)
139 scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024) // 10MB max line size
140
141 count := 0
142 for scanner.Scan() {
143 line := scanner.Bytes()
144 if len(line) == 0 {
145 continue
146 }
147
148 // Skip comments
149 if line[0] == '#' {
150 continue
151 }
152
153 // Parse event
154 ev := &event.E{}
155 if err := json.Unmarshal(line, ev); err != nil {
156 n.Logger.Warningf("failed to parse event: %v", err)
157 continue
158 }
159
160 // Save event
161 if _, err := n.SaveEvent(ctx, ev); err != nil {
162 n.Logger.Warningf("failed to import event: %v", err)
163 continue
164 }
165
166 count++
167 if count%1000 == 0 {
168 n.Logger.Infof("imported %d events", count)
169 }
170 }
171
172 if err := scanner.Err(); err != nil {
173 return fmt.Errorf("scanner error: %w", err)
174 }
175
176 n.Logger.Infof("import complete: %d events", count)
177 return nil
178 }
179
180 // ImportEventsFromStrings imports events from JSON strings
181 func (n *N) ImportEventsFromStrings(
182 ctx context.Context,
183 eventJSONs []string,
184 policyManager interface{ CheckPolicy(action string, ev *event.E, pubkey []byte, remote string) (bool, error) },
185 ) error {
186 for _, eventJSON := range eventJSONs {
187 ev := &event.E{}
188 if err := json.Unmarshal([]byte(eventJSON), ev); err != nil {
189 continue
190 }
191
192 // Check policy if manager is provided
193 if policyManager != nil {
194 if allowed, err := policyManager.CheckPolicy("write", ev, ev.Pubkey[:], "import"); err != nil || !allowed {
195 continue
196 }
197 }
198
199 // Save event
200 if _, err := n.SaveEvent(ctx, ev); err != nil {
201 n.Logger.Warningf("failed to import event: %v", err)
202 }
203 }
204
205 return nil
206 }
207