process-delete.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "bytes"
7 "context"
8 "sort"
9 "strconv"
10
11 "next.orly.dev/pkg/lol/chk"
12 "next.orly.dev/pkg/lol/log"
13 "next.orly.dev/pkg/database/indexes/types"
14 "next.orly.dev/pkg/nostr/encoders/event"
15 "next.orly.dev/pkg/nostr/encoders/filter"
16 hexenc "next.orly.dev/pkg/nostr/encoders/hex"
17 "next.orly.dev/pkg/nostr/encoders/ints"
18 "next.orly.dev/pkg/nostr/encoders/kind"
19 "next.orly.dev/pkg/nostr/encoders/tag"
20 "next.orly.dev/pkg/interfaces/store"
21 "next.orly.dev/pkg/utils"
22 )
23
24 func (d *D) ProcessDelete(ev *event.E, admins [][]byte) (err error) {
25 eTags := ev.Tags.GetAll([]byte("e"))
26 aTags := ev.Tags.GetAll([]byte("a"))
27 kTags := ev.Tags.GetAll([]byte("k"))
28
29 // Process e-tags: delete specific events by ID
30 for _, eTag := range eTags {
31 if eTag.Len() < 2 {
32 continue
33 }
34 // Use ValueHex() to handle both binary and hex storage formats
35 eventIdHex := eTag.ValueHex()
36 if len(eventIdHex) != 64 { // hex encoded event ID
37 continue
38 }
39 // Decode hex event ID
40 var eid []byte
41 if eid, err = hexenc.DecAppend(nil, eventIdHex); chk.E(err) {
42 continue
43 }
44 // Fetch the event to verify ownership
45 var ser *types.Uint40
46 if ser, err = d.GetSerialById(eid); chk.E(err) || ser == nil {
47 continue
48 }
49 var targetEv *event.E
50 if targetEv, err = d.FetchEventBySerial(ser); chk.E(err) || targetEv == nil {
51 continue
52 }
53 // Only allow users to delete their own events
54 if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) {
55 continue
56 }
57 // Delete the event
58 if err = d.DeleteEvent(context.Background(), eid); chk.E(err) {
59 log.W.F("failed to delete event %x via e-tag: %v", eid, err)
60 continue
61 }
62 log.D.F("deleted event %x via e-tag deletion", eid)
63 }
64
65 // Process a-tags: delete addressable events by kind:pubkey:d-tag
66 for _, aTag := range aTags {
67 if aTag.Len() < 2 {
68 continue
69 }
70 // Parse the 'a' tag value: kind:pubkey:d-tag (for parameterized) or kind:pubkey (for regular)
71 split := bytes.Split(aTag.Value(), []byte{':'})
72 if len(split) < 2 {
73 continue
74 }
75 // Parse the kind
76 kindStr := string(split[0])
77 kindInt, parseErr := strconv.Atoi(kindStr)
78 if parseErr != nil {
79 continue
80 }
81 kk := kind.New(uint16(kindInt))
82 // Parse the pubkey
83 var pk []byte
84 if pk, err = hexenc.DecAppend(nil, split[1]); chk.E(err) {
85 continue
86 }
87 // Only allow users to delete their own events
88 if !utils.FastEqual(pk, ev.Pubkey) {
89 continue
90 }
91
92 // Build filter for events to delete
93 delFilter := &filter.F{
94 Authors: tag.NewFromBytesSlice(pk),
95 Kinds: kind.NewS(kk),
96 }
97
98 // For parameterized replaceable events, add d-tag filter
99 if kind.IsParameterizedReplaceable(kk.K) && len(split) >= 3 {
100 dValue := split[2]
101 delFilter.Tags = tag.NewS(tag.NewFromAny([]byte("d"), dValue))
102 }
103
104 // Find matching events
105 var idxs []Range
106 if idxs, err = GetIndexesFromFilter(delFilter); chk.E(err) {
107 continue
108 }
109 var sers types.Uint40s
110 for _, idx := range idxs {
111 var s types.Uint40s
112 if s, err = d.GetSerialsByRange(idx); chk.E(err) {
113 continue
114 }
115 sers = append(sers, s...)
116 }
117
118 // Delete events older than the deletion event
119 if len(sers) > 0 {
120 var idPkTss []*store.IdPkTs
121 var tmp []*store.IdPkTs
122 if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) {
123 continue
124 }
125 idPkTss = append(idPkTss, tmp...)
126 // Sort by timestamp
127 sort.Slice(idPkTss, func(i, j int) bool {
128 return idPkTss[i].Ts > idPkTss[j].Ts
129 })
130 for _, v := range idPkTss {
131 if v.Ts < ev.CreatedAt {
132 if err = d.DeleteEvent(context.Background(), v.Id[:]); chk.E(err) {
133 log.W.F("failed to delete event %x via a-tag: %v", v.Id[:], err)
134 continue
135 }
136 log.D.F("deleted event %x via a-tag deletion", v.Id[:])
137 }
138 }
139 }
140 }
141
142 // if there are no e or a tags, we assume the intent is to delete all
143 // replaceable events of the kinds specified by the k tags for the pubkey of
144 // the delete event.
145 if len(eTags) == 0 && len(aTags) == 0 {
146 // parse the kind tags
147 var kinds []*kind.K
148 for _, k := range kTags {
149 kv := k.Value()
150 iv := ints.New(0)
151 if _, err = iv.Unmarshal(kv); chk.E(err) {
152 continue
153 }
154 kinds = append(kinds, kind.New(iv.N))
155 }
156 var idxs []Range
157 if idxs, err = GetIndexesFromFilter(
158 &filter.F{
159 Authors: tag.NewFromBytesSlice(ev.Pubkey),
160 Kinds: kind.NewS(kinds...),
161 },
162 ); chk.E(err) {
163 return
164 }
165 var sers types.Uint40s
166 for _, idx := range idxs {
167 var s types.Uint40s
168 if s, err = d.GetSerialsByRange(idx); chk.E(err) {
169 return
170 }
171 sers = append(sers, s...)
172 }
173 if len(sers) > 0 {
174 var idPkTss []*store.IdPkTs
175 var tmp []*store.IdPkTs
176 if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) {
177 return
178 }
179 idPkTss = append(idPkTss, tmp...)
180 // sort by timestamp, so the first is the oldest, so we can collect
181 // all of them until the delete event created_at.
182 sort.Slice(
183 idPkTss, func(i, j int) bool {
184 return idPkTss[i].Ts > idPkTss[j].Ts
185 },
186 )
187 for _, v := range idPkTss {
188 if v.Ts < ev.CreatedAt {
189 if err = d.DeleteEvent(
190 context.Background(), v.Id[:],
191 ); chk.E(err) {
192 continue
193 }
194 }
195 }
196 }
197 }
198 return
199 }
200