delete-event.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"bytes"
   7  	"context"
   8  
   9  	"github.com/dgraph-io/badger/v4"
  10  	"next.orly.dev/pkg/lol/chk"
  11  	"next.orly.dev/pkg/lol/log"
  12  	"next.orly.dev/pkg/database/indexes"
  13  	"next.orly.dev/pkg/database/indexes/types"
  14  	"next.orly.dev/pkg/nostr/encoders/event"
  15  	"next.orly.dev/pkg/nostr/encoders/hex"
  16  )
  17  
  18  // DeleteEvent removes an event from the database identified by `eid`. If
  19  // noTombstone is false or not provided, a tombstone is created for the event.
  20  func (d *D) DeleteEvent(c context.Context, eid []byte) (err error) {
  21  	d.Logger.Warningf("deleting event %0x", eid)
  22  
  23  	// Get the serial number for the event ID
  24  	var ser *types.Uint40
  25  	ser, err = d.GetSerialById(eid)
  26  	if chk.E(err) {
  27  		return
  28  	}
  29  	if ser == nil {
  30  		// Event wasn't found, nothing to delete
  31  		return
  32  	}
  33  	// Fetch the event to get its data
  34  	var ev *event.E
  35  	ev, err = d.FetchEventBySerial(ser)
  36  	if chk.E(err) {
  37  		return
  38  	}
  39  	if ev == nil {
  40  		// Event wasn't found, nothing to delete. this shouldn't happen.
  41  		return
  42  	}
  43  	if err = d.DeleteEventBySerial(c, ser, ev); chk.E(err) {
  44  		return
  45  	}
  46  	return
  47  }
  48  
  49  func (d *D) DeleteEventBySerial(
  50  	c context.Context, ser *types.Uint40, ev *event.E,
  51  ) (err error) {
  52  	d.Logger.Infof("DeleteEventBySerial: deleting event %0x (serial %d)", ev.ID, ser.Get())
  53  
  54  	// Get all indexes for the event
  55  	var idxs [][]byte
  56  	idxs, err = GetIndexesForEvent(ev, ser.Get())
  57  	if chk.E(err) {
  58  		d.Logger.Errorf("DeleteEventBySerial: failed to get indexes for event %0x: %v", ev.ID, err)
  59  		return
  60  	}
  61  	d.Logger.Infof("DeleteEventBySerial: found %d indexes for event %0x", len(idxs), ev.ID)
  62  
  63  	// Get the event key
  64  	eventKey := new(bytes.Buffer)
  65  	if err = indexes.EventEnc(ser).MarshalWrite(eventKey); chk.E(err) {
  66  		d.Logger.Errorf("DeleteEventBySerial: failed to create event key for %0x: %v", ev.ID, err)
  67  		return
  68  	}
  69  
  70  	// Resolve author serial for ppg/gpp cleanup
  71  	var authorSerial *types.Uint40
  72  	authorSerial, _ = d.GetPubkeySerial(ev.Pubkey)
  73  
  74  	// Delete the event and all its indexes in a transaction
  75  	err = d.Update(
  76  		func(txn *badger.Txn) (err error) {
  77  			// Delete the event
  78  			if err = txn.Delete(eventKey.Bytes()); chk.E(err) {
  79  				d.Logger.Errorf("DeleteEventBySerial: failed to delete event %0x: %v", ev.ID, err)
  80  				return
  81  			}
  82  			d.Logger.Infof("DeleteEventBySerial: deleted event %0x", ev.ID)
  83  
  84  			// Delete all standard indexes
  85  			for i, key := range idxs {
  86  				if err = txn.Delete(key); chk.E(err) {
  87  					d.Logger.Errorf("DeleteEventBySerial: failed to delete index %d for event %0x: %v", i, ev.ID, err)
  88  					return
  89  				}
  90  			}
  91  			d.Logger.Infof("DeleteEventBySerial: deleted %d indexes for event %0x", len(idxs), ev.ID)
  92  
  93  			// Delete graph indexes (epg/peg, ppg/gpp, eeg/gee)
  94  			graphDeleted := deleteGraphIndexes(txn, ser, ev, authorSerial)
  95  			if graphDeleted > 0 {
  96  				log.D.F("DeleteEventBySerial: deleted %d graph index entries for event %0x", graphDeleted, ev.ID)
  97  			}
  98  
  99  			return
 100  		},
 101  	)
 102  	if chk.E(err) {
 103  		d.Logger.Errorf("DeleteEventBySerial: transaction failed for event %0x: %v", ev.ID, err)
 104  		return
 105  	}
 106  
 107  	d.Logger.Infof("DeleteEventBySerial: successfully deleted event %0x and all indexes", ev.ID)
 108  	return
 109  }
 110  
 111  // deleteGraphIndexes removes all graph index entries (epg/peg, ppg/gpp, eeg/gee)
 112  // associated with the given event serial. These indexes are written directly in
 113  // save-event.go and not covered by GetIndexesForEvent.
 114  //
 115  // Returns the number of index entries deleted.
 116  func deleteGraphIndexes(txn *badger.Txn, ser *types.Uint40, ev *event.E, authorSerial *types.Uint40) int {
 117  	deleted := 0
 118  
 119  	// Serialize the event serial once for byte comparisons
 120  	serBuf := new(bytes.Buffer)
 121  	if err := ser.MarshalWrite(serBuf); err != nil {
 122  		return 0
 123  	}
 124  	serBytes := serBuf.Bytes() // 5 bytes
 125  
 126  	// --- epg: event→pubkey graph ---
 127  	// Key: epg(3)|event_serial(5)|pubkey_serial(5)|kind(2)|direction(1) = 16 bytes
 128  	// Prefix scan on epg|eventSerial finds all pubkey edges for this event.
 129  	// For each, construct and delete the reverse peg key.
 130  	epgPrefix := append([]byte(indexes.EventPubkeyGraphPrefix), serBytes...)
 131  	deleted += deleteByPrefixWithReverse(txn, epgPrefix, 16, func(key []byte) []byte {
 132  		// Extract fields from epg key to construct peg reverse key
 133  		// epg: [0:3]=prefix [3:8]=event_serial [8:13]=pubkey_serial [13:15]=kind [15:16]=direction
 134  		pubkeySerial := key[8:13]
 135  		kind := key[13:15]
 136  		direction := key[15:16]
 137  		// peg: peg(3)|pubkey_serial(5)|kind(2)|direction(1)|event_serial(5) = 16 bytes
 138  		rev := make([]byte, 16)
 139  		copy(rev[0:3], indexes.PubkeyEventGraphPrefix)
 140  		copy(rev[3:8], pubkeySerial)
 141  		copy(rev[8:10], kind)
 142  		copy(rev[10:11], direction)
 143  		copy(rev[11:16], serBytes)
 144  		return rev
 145  	})
 146  
 147  	// --- eeg: event→event graph (outbound e-tags) ---
 148  	// Key: eeg(3)|source_event(5)|target_event(5)|kind(2)|direction(1) = 16 bytes
 149  	// Prefix scan on eeg|eventSerial finds all outbound e-tag edges.
 150  	eegPrefix := append([]byte(indexes.EventEventGraphPrefix), serBytes...)
 151  	deleted += deleteByPrefixWithReverse(txn, eegPrefix, 16, func(key []byte) []byte {
 152  		// eeg: [0:3]=prefix [3:8]=source_serial [8:13]=target_serial [13:15]=kind [15:16]=direction
 153  		targetSerial := key[8:13]
 154  		kind := key[13:15]
 155  		// Reverse direction: outbound becomes inbound
 156  		dirIn := []byte{types.EdgeDirectionETagIn}
 157  		// gee: gee(3)|target_event(5)|kind(2)|direction(1)|source_event(5) = 16 bytes
 158  		rev := make([]byte, 16)
 159  		copy(rev[0:3], indexes.GraphEventEventPrefix)
 160  		copy(rev[3:8], targetSerial)
 161  		copy(rev[8:10], kind)
 162  		copy(rev[10:11], dirIn)
 163  		copy(rev[11:16], serBytes)
 164  		return rev
 165  	})
 166  
 167  	// --- gee: reverse event→event (inbound e-tags referencing this event) ---
 168  	// Key: gee(3)|target_event(5)|kind(2)|direction(1)|source_event(5) = 16 bytes
 169  	// Prefix scan on gee|eventSerial finds all events that reference this event.
 170  	geePrefix := append([]byte(indexes.GraphEventEventPrefix), serBytes...)
 171  	deleted += deleteByPrefixWithReverse(txn, geePrefix, 16, func(key []byte) []byte {
 172  		// gee: [0:3]=prefix [3:8]=target_serial [8:10]=kind [10:11]=direction [11:16]=source_serial
 173  		kind := key[8:10]
 174  		sourceSerial := key[11:16]
 175  		// Reverse: eeg(3)|source_serial(5)|target_serial(5)|kind(2)|direction(1)
 176  		dirOut := []byte{types.EdgeDirectionETagOut}
 177  		rev := make([]byte, 16)
 178  		copy(rev[0:3], indexes.EventEventGraphPrefix)
 179  		copy(rev[3:8], sourceSerial)
 180  		copy(rev[8:13], serBytes) // this event is the target
 181  		copy(rev[13:15], kind)
 182  		copy(rev[15:16], dirOut)
 183  		return rev
 184  	})
 185  
 186  	// --- ppg: pubkey→pubkey graph (outbound, keyed by author) ---
 187  	// Key: ppg(3)|source_pk(5)|target_pk(5)|kind(2)|direction(1)|event_serial(5) = 21 bytes
 188  	// We scan ppg|authorSerial and filter by trailing event_serial matching ser.
 189  	if authorSerial != nil {
 190  		authorBuf := new(bytes.Buffer)
 191  		if err := authorSerial.MarshalWrite(authorBuf); err == nil {
 192  			ppgPrefix := append([]byte(indexes.PubkeyPubkeyGraphPrefix), authorBuf.Bytes()...)
 193  			deleted += deleteByPrefixFilterSerial(txn, ppgPrefix, 21, serBytes, func(key []byte) []byte {
 194  				// ppg: [0:3]=prefix [3:8]=source_pk [8:13]=target_pk [13:15]=kind [15:16]=direction [16:21]=event_serial
 195  				targetPk := key[8:13]
 196  				kind := key[13:15]
 197  				// Reverse direction for gpp
 198  				dirIn := []byte{types.EdgeDirectionPubkeyIn}
 199  				// gpp: gpp(3)|target_pk(5)|kind(2)|direction(1)|source_pk(5)|event_serial(5) = 21 bytes
 200  				rev := make([]byte, 21)
 201  				copy(rev[0:3], indexes.GraphPubkeyPubkeyPrefix)
 202  				copy(rev[3:8], targetPk)
 203  				copy(rev[8:10], kind)
 204  				copy(rev[10:11], dirIn)
 205  				copy(rev[11:16], authorBuf.Bytes()) // source = author
 206  				copy(rev[16:21], serBytes)
 207  				return rev
 208  			})
 209  		}
 210  	}
 211  
 212  	// --- peg entries where this event appears as target ---
 213  	// peg keys that reference our event serial are at the end: peg(3)|pk(5)|kind(2)|dir(1)|event_serial(5)
 214  	// We can't efficiently prefix-scan for these. However, we already found and deleted all
 215  	// epg entries above, which gave us the pubkey serials. The reverse peg entries were
 216  	// constructed and deleted from those. For any peg entries where this event is referenced
 217  	// by OTHER events' pubkey graphs, those would be cleaned up when those events are deleted.
 218  
 219  	// --- sei: serial→eventID ---
 220  	seiBuf := new(bytes.Buffer)
 221  	seiBuf.Write([]byte(indexes.SerialEventIdPrefix))
 222  	seiBuf.Write(serBytes)
 223  	if err := txn.Delete(seiBuf.Bytes()); err == nil {
 224  		deleted++
 225  	}
 226  
 227  	// --- cmp: compact event storage ---
 228  	cmpBuf := new(bytes.Buffer)
 229  	cmpBuf.Write([]byte(indexes.CompactEventPrefix))
 230  	cmpBuf.Write(serBytes)
 231  	if err := txn.Delete(cmpBuf.Bytes()); err == nil {
 232  		deleted++
 233  	}
 234  
 235  	// --- aev: addressable event index (kinds 30000-39999) ---
 236  	if ev.Kind >= 30000 && ev.Kind < 40000 {
 237  		dTag := ev.Tags.GetFirst([]byte("d"))
 238  		if dTag != nil && dTag.Len() >= 2 {
 239  			pubHash := new(types.PubHash)
 240  			if err := pubHash.FromPubkey(ev.Pubkey); err == nil {
 241  				kind := new(types.Uint16)
 242  				kind.Set(ev.Kind)
 243  				ident := new(types.Ident)
 244  				ident.FromIdent(dTag.Value())
 245  				aevKey := new(bytes.Buffer)
 246  				if err := indexes.AddressableEventEnc(pubHash, kind, ident).MarshalWrite(aevKey); err == nil {
 247  					if err := txn.Delete(aevKey.Bytes()); err == nil {
 248  						deleted++
 249  					}
 250  				}
 251  			}
 252  		}
 253  	}
 254  
 255  	if deleted > 0 {
 256  		log.D.F("deleteGraphIndexes: cleaned %d graph/compact/addressable entries for event %s",
 257  			deleted, hex.Enc(ev.ID))
 258  	}
 259  
 260  	return deleted
 261  }
 262  
 263  // deleteByPrefixWithReverse scans all keys matching prefix, deletes each, and
 264  // constructs+deletes a reverse key using the provided function.
 265  func deleteByPrefixWithReverse(txn *badger.Txn, prefix []byte, expectedLen int, reverseKeyFn func([]byte) []byte) int {
 266  	deleted := 0
 267  	opts := badger.DefaultIteratorOptions
 268  	opts.PrefetchValues = false
 269  	opts.Prefix = prefix
 270  
 271  	it := txn.NewIterator(opts)
 272  	defer it.Close()
 273  
 274  	// Collect keys first (can't delete while iterating)
 275  	var keys [][]byte
 276  	for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 277  		key := it.Item().KeyCopy(nil)
 278  		if len(key) != expectedLen {
 279  			continue
 280  		}
 281  		keys = append(keys, key)
 282  	}
 283  
 284  	for _, key := range keys {
 285  		if err := txn.Delete(key); err == nil {
 286  			deleted++
 287  		}
 288  		rev := reverseKeyFn(key)
 289  		if rev != nil {
 290  			if err := txn.Delete(rev); err == nil {
 291  				deleted++
 292  			}
 293  		}
 294  	}
 295  	return deleted
 296  }
 297  
 298  // deleteByPrefixFilterSerial scans keys matching prefix, filters by trailing
 299  // event serial (last 5 bytes), deletes matches, and constructs+deletes reverse keys.
 300  func deleteByPrefixFilterSerial(txn *badger.Txn, prefix []byte, expectedLen int, serialBytes []byte, reverseKeyFn func([]byte) []byte) int {
 301  	deleted := 0
 302  	opts := badger.DefaultIteratorOptions
 303  	opts.PrefetchValues = false
 304  	opts.Prefix = prefix
 305  
 306  	it := txn.NewIterator(opts)
 307  	defer it.Close()
 308  
 309  	var keys [][]byte
 310  	for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 311  		key := it.Item().KeyCopy(nil)
 312  		if len(key) != expectedLen {
 313  			continue
 314  		}
 315  		// Check if trailing 5 bytes match the event serial
 316  		if !bytes.Equal(key[expectedLen-5:], serialBytes) {
 317  			continue
 318  		}
 319  		keys = append(keys, key)
 320  	}
 321  
 322  	for _, key := range keys {
 323  		if err := txn.Delete(key); err == nil {
 324  			deleted++
 325  		}
 326  		rev := reverseKeyFn(key)
 327  		if rev != nil {
 328  			if err := txn.Delete(rev); err == nil {
 329  				deleted++
 330  			}
 331  		}
 332  	}
 333  	return deleted
 334  }
 335