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