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