You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
5.0 KiB
197 lines
5.0 KiB
package database |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"sort" |
|
"strconv" |
|
|
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/log" |
|
"next.orly.dev/pkg/database/indexes/types" |
|
"git.mleku.dev/mleku/nostr/encoders/event" |
|
"git.mleku.dev/mleku/nostr/encoders/filter" |
|
hexenc "git.mleku.dev/mleku/nostr/encoders/hex" |
|
"git.mleku.dev/mleku/nostr/encoders/ints" |
|
"git.mleku.dev/mleku/nostr/encoders/kind" |
|
"git.mleku.dev/mleku/nostr/encoders/tag" |
|
"next.orly.dev/pkg/interfaces/store" |
|
"next.orly.dev/pkg/utils" |
|
) |
|
|
|
func (d *D) ProcessDelete(ev *event.E, admins [][]byte) (err error) { |
|
eTags := ev.Tags.GetAll([]byte("e")) |
|
aTags := ev.Tags.GetAll([]byte("a")) |
|
kTags := ev.Tags.GetAll([]byte("k")) |
|
|
|
// Process e-tags: delete specific events by ID |
|
for _, eTag := range eTags { |
|
if eTag.Len() < 2 { |
|
continue |
|
} |
|
// Use ValueHex() to handle both binary and hex storage formats |
|
eventIdHex := eTag.ValueHex() |
|
if len(eventIdHex) != 64 { // hex encoded event ID |
|
continue |
|
} |
|
// Decode hex event ID |
|
var eid []byte |
|
if eid, err = hexenc.DecAppend(nil, eventIdHex); chk.E(err) { |
|
continue |
|
} |
|
// Fetch the event to verify ownership |
|
var ser *types.Uint40 |
|
if ser, err = d.GetSerialById(eid); chk.E(err) || ser == nil { |
|
continue |
|
} |
|
var targetEv *event.E |
|
if targetEv, err = d.FetchEventBySerial(ser); chk.E(err) || targetEv == nil { |
|
continue |
|
} |
|
// Only allow users to delete their own events |
|
if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) { |
|
continue |
|
} |
|
// Delete the event |
|
if err = d.DeleteEvent(context.Background(), eid); chk.E(err) { |
|
log.W.F("failed to delete event %x via e-tag: %v", eid, err) |
|
continue |
|
} |
|
log.D.F("deleted event %x via e-tag deletion", eid) |
|
} |
|
|
|
// Process a-tags: delete addressable events by kind:pubkey:d-tag |
|
for _, aTag := range aTags { |
|
if aTag.Len() < 2 { |
|
continue |
|
} |
|
// Parse the 'a' tag value: kind:pubkey:d-tag (for parameterized) or kind:pubkey (for regular) |
|
split := bytes.Split(aTag.Value(), []byte{':'}) |
|
if len(split) < 2 { |
|
continue |
|
} |
|
// Parse the kind |
|
kindStr := string(split[0]) |
|
kindInt, parseErr := strconv.Atoi(kindStr) |
|
if parseErr != nil { |
|
continue |
|
} |
|
kk := kind.New(uint16(kindInt)) |
|
// Parse the pubkey |
|
var pk []byte |
|
if pk, err = hexenc.DecAppend(nil, split[1]); chk.E(err) { |
|
continue |
|
} |
|
// Only allow users to delete their own events |
|
if !utils.FastEqual(pk, ev.Pubkey) { |
|
continue |
|
} |
|
|
|
// Build filter for events to delete |
|
delFilter := &filter.F{ |
|
Authors: tag.NewFromBytesSlice(pk), |
|
Kinds: kind.NewS(kk), |
|
} |
|
|
|
// For parameterized replaceable events, add d-tag filter |
|
if kind.IsParameterizedReplaceable(kk.K) && len(split) >= 3 { |
|
dValue := split[2] |
|
delFilter.Tags = tag.NewS(tag.NewFromAny([]byte("d"), dValue)) |
|
} |
|
|
|
// Find matching events |
|
var idxs []Range |
|
if idxs, err = GetIndexesFromFilter(delFilter); chk.E(err) { |
|
continue |
|
} |
|
var sers types.Uint40s |
|
for _, idx := range idxs { |
|
var s types.Uint40s |
|
if s, err = d.GetSerialsByRange(idx); chk.E(err) { |
|
continue |
|
} |
|
sers = append(sers, s...) |
|
} |
|
|
|
// Delete events older than the deletion event |
|
if len(sers) > 0 { |
|
var idPkTss []*store.IdPkTs |
|
var tmp []*store.IdPkTs |
|
if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) { |
|
continue |
|
} |
|
idPkTss = append(idPkTss, tmp...) |
|
// Sort by timestamp |
|
sort.Slice(idPkTss, func(i, j int) bool { |
|
return idPkTss[i].Ts > idPkTss[j].Ts |
|
}) |
|
for _, v := range idPkTss { |
|
if v.Ts < ev.CreatedAt { |
|
if err = d.DeleteEvent(context.Background(), v.Id); chk.E(err) { |
|
log.W.F("failed to delete event %x via a-tag: %v", v.Id, err) |
|
continue |
|
} |
|
log.D.F("deleted event %x via a-tag deletion", v.Id) |
|
} |
|
} |
|
} |
|
} |
|
|
|
// if there are no e or a tags, we assume the intent is to delete all |
|
// replaceable events of the kinds specified by the k tags for the pubkey of |
|
// the delete event. |
|
if len(eTags) == 0 && len(aTags) == 0 { |
|
// parse the kind tags |
|
var kinds []*kind.K |
|
for _, k := range kTags { |
|
kv := k.Value() |
|
iv := ints.New(0) |
|
if _, err = iv.Unmarshal(kv); chk.E(err) { |
|
continue |
|
} |
|
kinds = append(kinds, kind.New(iv.N)) |
|
} |
|
var idxs []Range |
|
if idxs, err = GetIndexesFromFilter( |
|
&filter.F{ |
|
Authors: tag.NewFromBytesSlice(ev.Pubkey), |
|
Kinds: kind.NewS(kinds...), |
|
}, |
|
); chk.E(err) { |
|
return |
|
} |
|
var sers types.Uint40s |
|
for _, idx := range idxs { |
|
var s types.Uint40s |
|
if s, err = d.GetSerialsByRange(idx); chk.E(err) { |
|
return |
|
} |
|
sers = append(sers, s...) |
|
} |
|
if len(sers) > 0 { |
|
var idPkTss []*store.IdPkTs |
|
var tmp []*store.IdPkTs |
|
if tmp, err = d.GetFullIdPubkeyBySerials(sers); chk.E(err) { |
|
return |
|
} |
|
idPkTss = append(idPkTss, tmp...) |
|
// sort by timestamp, so the first is the oldest, so we can collect |
|
// all of them until the delete event created_at. |
|
sort.Slice( |
|
idPkTss, func(i, j int) bool { |
|
return idPkTss[i].Ts > idPkTss[j].Ts |
|
}, |
|
) |
|
for _, v := range idPkTss { |
|
if v.Ts < ev.CreatedAt { |
|
if err = d.DeleteEvent( |
|
context.Background(), v.Id, |
|
); chk.E(err) { |
|
continue |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return |
|
}
|
|
|