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