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