handle-delete.go raw
1 package app
2
3 import (
4 "next.orly.dev/pkg/lol/chk"
5 "next.orly.dev/pkg/lol/log"
6 "next.orly.dev/pkg/database/indexes/types"
7 "next.orly.dev/pkg/nostr/encoders/envelopes/eventenvelope"
8 "next.orly.dev/pkg/nostr/encoders/event"
9 "next.orly.dev/pkg/nostr/encoders/filter"
10 "next.orly.dev/pkg/nostr/encoders/hex"
11 "next.orly.dev/pkg/nostr/encoders/ints"
12 "next.orly.dev/pkg/nostr/encoders/kind"
13 "next.orly.dev/pkg/nostr/encoders/tag"
14 "next.orly.dev/pkg/nostr/encoders/tag/atag"
15 utils "next.orly.dev/pkg/utils"
16 )
17
18 func (l *Listener) GetSerialsFromFilter(f *filter.F) (
19 sers types.Uint40s, err error,
20 ) {
21 return l.DB.GetSerialsFromFilter(f)
22 }
23
24 func (l *Listener) HandleDelete(env *eventenvelope.Submission) (err error) {
25 log.D.F("HandleDelete: processing delete event %0x from pubkey %0x", env.E.ID, env.E.Pubkey)
26 log.T.F("HandleDelete: delete event tags: %d tags", len(*env.E.Tags))
27 for i, t := range *env.E.Tags {
28 // Use ValueHex() for e/p tags to properly display binary-encoded values
29 key := string(t.Key())
30 var val string
31 if key == "e" || key == "p" {
32 val = string(t.ValueHex()) // Properly converts binary to hex
33 } else {
34 val = string(t.Value())
35 }
36 log.T.F("HandleDelete: tag %d: %s = %s", i, key, val)
37 }
38
39 log.T.F("HandleDelete: checking against %d admins and %d owners", len(l.Admins), len(l.Owners))
40
41 var ownerDelete bool
42 for _, pk := range l.Admins {
43 if utils.FastEqual(pk, env.E.Pubkey) {
44 ownerDelete = true
45 log.D.F("HandleDelete: delete event from admin/owner %0x", env.E.Pubkey)
46 break
47 }
48 }
49 if !ownerDelete {
50 for _, pk := range l.Owners {
51 if utils.FastEqual(pk, env.E.Pubkey) {
52 ownerDelete = true
53 log.D.F("HandleDelete: delete event from owner %0x", env.E.Pubkey)
54 break
55 }
56 }
57 }
58 if !ownerDelete {
59 log.D.F("HandleDelete: delete event from regular user %0x", env.E.Pubkey)
60 }
61 // process the tags in the delete event
62 var deleteErr error
63 var validDeletionFound bool
64 var deletionCount int
65 for _, t := range *env.E.Tags {
66 // first search for a tags, as these are the simplest to process
67 if utils.FastEqual(t.Key(), []byte("a")) {
68 at := new(atag.T)
69 if _, deleteErr = at.Unmarshal(t.Value()); chk.E(deleteErr) {
70 continue
71 }
72 if ownerDelete || utils.FastEqual(env.E.Pubkey, at.Pubkey) {
73 validDeletionFound = true
74 // find the event and delete it
75 f := &filter.F{
76 Authors: tag.NewFromBytesSlice(at.Pubkey),
77 Kinds: kind.NewS(at.Kind),
78 }
79 if len(at.DTag) > 0 {
80 f.Tags = tag.NewS(
81 tag.NewFromAny("d", at.DTag),
82 )
83 }
84 var sers types.Uint40s
85 if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
86 continue
87 }
88 // if found, delete them
89 if len(sers) > 0 {
90 for _, s := range sers {
91 var ev *event.E
92 if ev, err = l.DB.FetchEventBySerial(s); chk.E(err) {
93 continue
94 }
95 // Only delete events that match the a-tag criteria:
96 // - For parameterized replaceable events: must have matching d-tag
97 // - For regular replaceable events: should not have d-tag constraint
98 if kind.IsParameterizedReplaceable(ev.Kind) {
99 // For parameterized replaceable, we need a DTag to match
100 if len(at.DTag) == 0 {
101 log.I.F(
102 "HandleDelete: skipping parameterized replaceable event %s - no DTag in a-tag",
103 hex.Enc(ev.ID),
104 )
105 continue
106 }
107 } else if !kind.IsReplaceable(ev.Kind) {
108 // For non-replaceable events, a-tags don't apply
109 log.I.F(
110 "HandleDelete: skipping non-replaceable event %s - a-tags only apply to replaceable events",
111 hex.Enc(ev.ID),
112 )
113 continue
114 }
115
116 // Only delete events that are older than or equal to the delete event timestamp
117 if ev.CreatedAt > env.E.CreatedAt {
118 log.I.F(
119 "HandleDelete: skipping newer event %s (created_at=%d) - delete event timestamp is %d",
120 hex.Enc(ev.ID), ev.CreatedAt, env.E.CreatedAt,
121 )
122 continue
123 }
124
125 log.I.F(
126 "HandleDelete: deleting event %s via a-tag %d:%s:%s (event_time=%d, delete_time=%d)",
127 hex.Enc(ev.ID), at.Kind.K, hex.Enc(at.Pubkey),
128 string(at.DTag), ev.CreatedAt, env.E.CreatedAt,
129 )
130 if err = l.DB.DeleteEventBySerial(
131 l.Ctx(), s, ev,
132 ); chk.E(err) {
133 log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err)
134 continue
135 }
136 deletionCount++
137 }
138 }
139 }
140 continue
141 }
142 // if e tags are found, delete them if the author is signer, or one of
143 // the owners is signer
144 if utils.FastEqual(t.Key(), []byte("e")) {
145 // Use ValueHex() which properly handles both binary-encoded and hex string formats
146 hexVal := t.ValueHex()
147 if len(hexVal) == 0 {
148 log.W.F("HandleDelete: empty e-tag value")
149 continue
150 }
151 log.D.F("HandleDelete: processing e-tag event ID: %s", string(hexVal))
152
153 // Decode hex to binary for filter
154 dst, e := hex.Dec(string(hexVal))
155 if chk.E(e) {
156 log.E.F("HandleDelete: failed to decode event ID %s: %v", string(hexVal), e)
157 continue
158 }
159
160 f := &filter.F{
161 Ids: tag.NewFromBytesSlice(dst),
162 }
163 var sers types.Uint40s
164 if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
165 log.E.F("HandleDelete: failed to get serials from filter: %v", err)
166 continue
167 }
168 log.D.F("HandleDelete: found %d serials for event ID %0x", len(sers), dst)
169 // if found, delete them
170 if len(sers) > 0 {
171 // there should be only one event per serial, so we can just
172 // delete them all
173 for _, s := range sers {
174 var ev *event.E
175 if ev, err = l.DB.FetchEventBySerial(s); chk.E(err) {
176 continue
177 }
178 // Debug: log the comparison details
179 log.D.F("HandleDelete: checking deletion permission for event %s", hex.Enc(ev.ID))
180 log.D.F("HandleDelete: delete event pubkey = %s, target event pubkey = %s", hex.Enc(env.E.Pubkey), hex.Enc(ev.Pubkey))
181 log.D.F("HandleDelete: ownerDelete = %v, pubkey match = %v", ownerDelete, utils.FastEqual(env.E.Pubkey, ev.Pubkey))
182
183 // For admin/owner deletes: allow deletion regardless of pubkey match
184 // For regular users: allow deletion only if the signer is the author
185 if !ownerDelete && !utils.FastEqual(env.E.Pubkey, ev.Pubkey) {
186 log.W.F(
187 "HandleDelete: attempted deletion of event %s by unauthorized user - delete pubkey=%s, event pubkey=%s",
188 hex.Enc(ev.ID), hex.Enc(env.E.Pubkey),
189 hex.Enc(ev.Pubkey),
190 )
191 continue
192 }
193 log.D.F("HandleDelete: deletion authorized for event %s", hex.Enc(ev.ID))
194 validDeletionFound = true
195 // exclude delete events
196 if ev.Kind == kind.EventDeletion.K {
197 continue
198 }
199 log.I.F(
200 "HandleDelete: deleting event %s by authorized user %s",
201 hex.Enc(ev.ID), hex.Enc(env.E.Pubkey),
202 )
203 if err = l.DB.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) {
204 log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err)
205 continue
206 }
207 deletionCount++
208 }
209 continue
210 }
211 }
212 // if k tags are found, check they are replaceable
213 if utils.FastEqual(t.Key(), []byte("k")) {
214 ki := ints.New(0)
215 if _, err = ki.Unmarshal(t.Value()); chk.E(err) {
216 continue
217 }
218 kn := ki.Uint16()
219 // skip events that are delete events or that are not replaceable
220 if !kind.IsReplaceable(kn) || kn != kind.EventDeletion.K {
221 continue
222 }
223 f := &filter.F{
224 Authors: tag.NewFromBytesSlice(env.E.Pubkey),
225 Kinds: kind.NewS(kind.New(kn)),
226 }
227 var sers types.Uint40s
228 if sers, err = l.GetSerialsFromFilter(f); chk.E(err) {
229 continue
230 }
231 // if found, delete them
232 if len(sers) > 0 {
233 // there should be only one event per serial because replaces
234 // delete old ones, so we can just delete them all
235 for _, s := range sers {
236 var ev *event.E
237 if ev, err = l.DB.FetchEventBySerial(s); chk.E(err) {
238 continue
239 }
240 // For admin/owner deletes: allow deletion regardless of pubkey match
241 // For regular users: allow deletion only if the signer is the author
242 if !ownerDelete && !utils.FastEqual(env.E.Pubkey, ev.Pubkey) {
243 continue
244 }
245 validDeletionFound = true
246 log.I.F(
247 "HandleDelete: deleting event %s via k-tag by authorized user %s",
248 hex.Enc(ev.ID), hex.Enc(env.E.Pubkey),
249 )
250 if err = l.DB.DeleteEventBySerial(l.Ctx(), s, ev); chk.E(err) {
251 log.E.F("HandleDelete: failed to delete event %s: %v", hex.Enc(ev.ID), err)
252 continue
253 }
254 deletionCount++
255 }
256 }
257 }
258 }
259
260 // If no valid deletions were found, return an error
261 if !validDeletionFound {
262 log.W.F("HandleDelete: no valid deletions found for event %0x", env.E.ID)
263 // Don't block delete events from being stored - just log the issue
264 // The delete event itself should still be accepted even if no targets are found
265 log.D.F("HandleDelete: delete event %0x stored but no target events found to delete", env.E.ID)
266 return nil
267 }
268
269 log.D.F("HandleDelete: successfully processed %d deletions for event %0x", deletionCount, env.E.ID)
270 return
271 }
272