implement wasm/js specific database engine
This commit is contained in:
614
pkg/wasmdb/delete-event.go
Normal file
614
pkg/wasmdb/delete-event.go
Normal file
@@ -0,0 +1,614 @@
|
||||
//go:build js && wasm
|
||||
|
||||
package wasmdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aperturerobotics/go-indexeddb/idb"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
|
||||
"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"
|
||||
"git.mleku.dev/mleku/nostr/encoders/tag/atag"
|
||||
"next.orly.dev/pkg/database"
|
||||
"next.orly.dev/pkg/database/indexes"
|
||||
"next.orly.dev/pkg/database/indexes/types"
|
||||
"next.orly.dev/pkg/interfaces/store"
|
||||
"next.orly.dev/pkg/utils"
|
||||
)
|
||||
|
||||
// DeleteEvent removes an event from the database identified by `eid`.
|
||||
func (w *W) DeleteEvent(c context.Context, eid []byte) (err error) {
|
||||
w.Logger.Warnf("deleting event %0x", eid)
|
||||
|
||||
// Get the serial number for the event ID
|
||||
var ser *types.Uint40
|
||||
ser, err = w.GetSerialById(eid)
|
||||
if chk.E(err) {
|
||||
return
|
||||
}
|
||||
if ser == nil {
|
||||
// Event wasn't found, nothing to delete
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch the event to get its data
|
||||
var ev *event.E
|
||||
ev, err = w.FetchEventBySerial(ser)
|
||||
if chk.E(err) {
|
||||
return
|
||||
}
|
||||
if ev == nil {
|
||||
// Event wasn't found, nothing to delete
|
||||
return
|
||||
}
|
||||
|
||||
if err = w.DeleteEventBySerial(c, ser, ev); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteEventBySerial removes an event and all its indexes by serial number.
|
||||
func (w *W) DeleteEventBySerial(c context.Context, ser *types.Uint40, ev *event.E) (err error) {
|
||||
w.Logger.Infof("DeleteEventBySerial: deleting event %0x (serial %d)", ev.ID, ser.Get())
|
||||
|
||||
// Get all indexes for the event
|
||||
var idxs [][]byte
|
||||
idxs, err = database.GetIndexesForEvent(ev, ser.Get())
|
||||
if chk.E(err) {
|
||||
w.Logger.Errorf("DeleteEventBySerial: failed to get indexes for event %0x: %v", ev.ID, err)
|
||||
return
|
||||
}
|
||||
w.Logger.Infof("DeleteEventBySerial: found %d indexes for event %0x", len(idxs), ev.ID)
|
||||
|
||||
// Collect all unique store names we need to access
|
||||
storeNames := make(map[string]struct{})
|
||||
for _, key := range idxs {
|
||||
if len(key) >= 3 {
|
||||
storeNames[string(key[:3])] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Also include event stores
|
||||
storeNames[string(indexes.EventPrefix)] = struct{}{}
|
||||
storeNames[string(indexes.SmallEventPrefix)] = struct{}{}
|
||||
|
||||
// Convert to slice
|
||||
storeList := make([]string, 0, len(storeNames))
|
||||
for name := range storeNames {
|
||||
storeList = append(storeList, name)
|
||||
}
|
||||
|
||||
if len(storeList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start a transaction to delete the event and all its indexes
|
||||
tx, err := w.db.Transaction(idb.TransactionReadWrite, storeList[0], storeList[1:]...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start delete transaction: %w", err)
|
||||
}
|
||||
|
||||
// Delete all indexes
|
||||
for _, key := range idxs {
|
||||
if len(key) < 3 {
|
||||
continue
|
||||
}
|
||||
storeName := string(key[:3])
|
||||
objStore, storeErr := tx.ObjectStore(storeName)
|
||||
if storeErr != nil {
|
||||
w.Logger.Warnf("DeleteEventBySerial: failed to get object store %s: %v", storeName, storeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
keyJS := bytesToSafeValue(key)
|
||||
if _, delErr := objStore.Delete(keyJS); delErr != nil {
|
||||
w.Logger.Warnf("DeleteEventBySerial: failed to delete index from %s: %v", storeName, delErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from small event store
|
||||
sevKeyBuf := new(bytes.Buffer)
|
||||
if err = indexes.SmallEventEnc(ser).MarshalWrite(sevKeyBuf); err == nil {
|
||||
if objStore, storeErr := tx.ObjectStore(string(indexes.SmallEventPrefix)); storeErr == nil {
|
||||
// For small events, the key includes size and data, so we need to scan
|
||||
w.deleteKeysByPrefix(objStore, sevKeyBuf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from large event store
|
||||
evtKeyBuf := new(bytes.Buffer)
|
||||
if err = indexes.EventEnc(ser).MarshalWrite(evtKeyBuf); err == nil {
|
||||
if objStore, storeErr := tx.ObjectStore(string(indexes.EventPrefix)); storeErr == nil {
|
||||
keyJS := bytesToSafeValue(evtKeyBuf.Bytes())
|
||||
objStore.Delete(keyJS)
|
||||
}
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
if err = tx.Await(c); err != nil {
|
||||
return fmt.Errorf("failed to commit delete transaction: %w", err)
|
||||
}
|
||||
|
||||
w.Logger.Infof("DeleteEventBySerial: successfully deleted event %0x and all indexes", ev.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteKeysByPrefix deletes all keys starting with the given prefix from an object store
|
||||
func (w *W) deleteKeysByPrefix(store *idb.ObjectStore, prefix []byte) {
|
||||
cursorReq, err := store.OpenCursor(idb.CursorNext)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var keysToDelete [][]byte
|
||||
cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
|
||||
keyVal, keyErr := cursor.Key()
|
||||
if keyErr != nil {
|
||||
return keyErr
|
||||
}
|
||||
|
||||
keyBytes := safeValueToBytes(keyVal)
|
||||
if len(keyBytes) >= len(prefix) && bytes.HasPrefix(keyBytes, prefix) {
|
||||
keysToDelete = append(keysToDelete, keyBytes)
|
||||
}
|
||||
|
||||
return cursor.Continue()
|
||||
})
|
||||
|
||||
// Delete collected keys
|
||||
for _, key := range keysToDelete {
|
||||
keyJS := bytesToSafeValue(key)
|
||||
store.Delete(keyJS)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteExpired scans for events with expiration timestamps that have passed and deletes them.
|
||||
func (w *W) DeleteExpired() {
|
||||
now := time.Now().Unix()
|
||||
|
||||
// Open read transaction to find expired events
|
||||
tx, err := w.db.Transaction(idb.TransactionReadOnly, string(indexes.ExpirationPrefix))
|
||||
if err != nil {
|
||||
w.Logger.Warnf("DeleteExpired: failed to start transaction: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
objStore, err := tx.ObjectStore(string(indexes.ExpirationPrefix))
|
||||
if err != nil {
|
||||
w.Logger.Warnf("DeleteExpired: failed to get expiration store: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var expiredSerials types.Uint40s
|
||||
|
||||
cursorReq, err := objStore.OpenCursor(idb.CursorNext)
|
||||
if err != nil {
|
||||
w.Logger.Warnf("DeleteExpired: failed to open cursor: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cursorReq.Iter(w.ctx, func(cursor *idb.CursorWithValue) error {
|
||||
keyVal, keyErr := cursor.Key()
|
||||
if keyErr != nil {
|
||||
return keyErr
|
||||
}
|
||||
|
||||
keyBytes := safeValueToBytes(keyVal)
|
||||
if len(keyBytes) < 8 { // exp prefix (3) + expiration (variable) + serial (5)
|
||||
return cursor.Continue()
|
||||
}
|
||||
|
||||
// Parse expiration key: exp|expiration_timestamp|serial
|
||||
exp, ser := indexes.ExpirationVars()
|
||||
buf := bytes.NewBuffer(keyBytes)
|
||||
if err := indexes.ExpirationDec(exp, ser).UnmarshalRead(buf); err != nil {
|
||||
return cursor.Continue()
|
||||
}
|
||||
|
||||
if int64(exp.Get()) > now {
|
||||
// Not expired yet
|
||||
return cursor.Continue()
|
||||
}
|
||||
|
||||
expiredSerials = append(expiredSerials, ser)
|
||||
return cursor.Continue()
|
||||
})
|
||||
|
||||
// Delete expired events
|
||||
for _, ser := range expiredSerials {
|
||||
ev, fetchErr := w.FetchEventBySerial(ser)
|
||||
if fetchErr != nil || ev == nil {
|
||||
continue
|
||||
}
|
||||
if err := w.DeleteEventBySerial(context.Background(), ser, ev); err != nil {
|
||||
w.Logger.Warnf("DeleteExpired: failed to delete expired event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessDelete processes a kind 5 deletion event, deleting referenced events.
|
||||
func (w *W) 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 = w.GetSerialById(eid); chk.E(err) || ser == nil {
|
||||
continue
|
||||
}
|
||||
var targetEv *event.E
|
||||
if targetEv, err = w.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 = w.DeleteEvent(context.Background(), eid); chk.E(err) {
|
||||
w.Logger.Warnf("failed to delete event %x via e-tag: %v", eid, err)
|
||||
continue
|
||||
}
|
||||
w.Logger.Debugf("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 []database.Range
|
||||
if idxs, err = database.GetIndexesFromFilter(delFilter); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
var sers types.Uint40s
|
||||
for _, idx := range idxs {
|
||||
var s types.Uint40s
|
||||
if s, err = w.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 = w.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 = w.DeleteEvent(context.Background(), v.Id); chk.E(err) {
|
||||
w.Logger.Warnf("failed to delete event %x via a-tag: %v", v.Id, err)
|
||||
continue
|
||||
}
|
||||
w.Logger.Debugf("deleted event %x via a-tag deletion", v.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no e or a tags, 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 []database.Range
|
||||
if idxs, err = database.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 = w.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 = w.GetFullIdPubkeyBySerials(sers); chk.E(err) {
|
||||
return
|
||||
}
|
||||
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 = w.DeleteEvent(context.Background(), v.Id); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CheckForDeleted checks if the event has been deleted, and returns an error with
|
||||
// prefix "blocked:" if it is. This function also allows designating admin
|
||||
// pubkeys that may also delete the event.
|
||||
func (w *W) CheckForDeleted(ev *event.E, admins [][]byte) (err error) {
|
||||
keys := append([][]byte{ev.Pubkey}, admins...)
|
||||
authors := tag.NewFromBytesSlice(keys...)
|
||||
|
||||
// If the event is addressable, check for a deletion event with the same
|
||||
// kind/pubkey/dtag
|
||||
if kind.IsParameterizedReplaceable(ev.Kind) {
|
||||
var idxs []database.Range
|
||||
// Construct an a-tag
|
||||
t := ev.Tags.GetFirst([]byte("d"))
|
||||
var dTagValue []byte
|
||||
if t != nil {
|
||||
dTagValue = t.Value()
|
||||
}
|
||||
a := atag.T{
|
||||
Kind: kind.New(ev.Kind),
|
||||
Pubkey: ev.Pubkey,
|
||||
DTag: dTagValue,
|
||||
}
|
||||
at := a.Marshal(nil)
|
||||
if idxs, err = database.GetIndexesFromFilter(
|
||||
&filter.F{
|
||||
Authors: authors,
|
||||
Kinds: kind.NewS(kind.Deletion),
|
||||
Tags: tag.NewS(tag.NewFromAny("#a", at)),
|
||||
},
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
|
||||
var sers types.Uint40s
|
||||
for _, idx := range idxs {
|
||||
var s types.Uint40s
|
||||
if s, err = w.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 = w.GetFullIdPubkeyBySerials(sers); chk.E(err) {
|
||||
return
|
||||
}
|
||||
idPkTss = append(idPkTss, tmp...)
|
||||
// Find the newest deletion timestamp
|
||||
maxTs := idPkTss[0].Ts
|
||||
for i := 1; i < len(idPkTss); i++ {
|
||||
if idPkTss[i].Ts > maxTs {
|
||||
maxTs = idPkTss[i].Ts
|
||||
}
|
||||
}
|
||||
if ev.CreatedAt < maxTs {
|
||||
err = errorf.E(
|
||||
"blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
|
||||
ev.ID, at, ev.CreatedAt, maxTs,
|
||||
)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If the event is replaceable, check if there is a deletion event newer
|
||||
// than the event
|
||||
if kind.IsReplaceable(ev.Kind) {
|
||||
var idxs []database.Range
|
||||
if idxs, err = database.GetIndexesFromFilter(
|
||||
&filter.F{
|
||||
Authors: tag.NewFromBytesSlice(ev.Pubkey),
|
||||
Kinds: kind.NewS(kind.Deletion),
|
||||
Tags: tag.NewS(
|
||||
tag.NewFromAny("#k", fmt.Sprint(ev.Kind)),
|
||||
),
|
||||
},
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
|
||||
var sers types.Uint40s
|
||||
for _, idx := range idxs {
|
||||
var s types.Uint40s
|
||||
if s, err = w.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 = w.GetFullIdPubkeyBySerials(sers); chk.E(err) {
|
||||
return
|
||||
}
|
||||
idPkTss = append(idPkTss, tmp...)
|
||||
// Find the newest deletion
|
||||
maxTs := idPkTss[0].Ts
|
||||
maxId := idPkTss[0].Id
|
||||
for i := 1; i < len(idPkTss); i++ {
|
||||
if idPkTss[i].Ts > maxTs {
|
||||
maxTs = idPkTss[i].Ts
|
||||
maxId = idPkTss[i].Id
|
||||
}
|
||||
}
|
||||
if ev.CreatedAt < maxTs {
|
||||
err = fmt.Errorf(
|
||||
"blocked: %0x was deleted: the event is older than the delete event %0x: event: %d delete: %d",
|
||||
ev.ID, maxId, ev.CreatedAt, maxTs,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This type of delete can also use an a tag to specify kind and author
|
||||
idxs = nil
|
||||
a := atag.T{
|
||||
Kind: kind.New(ev.Kind),
|
||||
Pubkey: ev.Pubkey,
|
||||
}
|
||||
at := a.Marshal(nil)
|
||||
if idxs, err = database.GetIndexesFromFilter(
|
||||
&filter.F{
|
||||
Authors: authors,
|
||||
Kinds: kind.NewS(kind.Deletion),
|
||||
Tags: tag.NewS(tag.NewFromAny("#a", at)),
|
||||
},
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
|
||||
sers = nil
|
||||
for _, idx := range idxs {
|
||||
var s types.Uint40s
|
||||
if s, err = w.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 = w.GetFullIdPubkeyBySerials(sers); chk.E(err) {
|
||||
return
|
||||
}
|
||||
idPkTss = append(idPkTss, tmp...)
|
||||
// Find the newest deletion
|
||||
maxTs := idPkTss[0].Ts
|
||||
for i := 1; i < len(idPkTss); i++ {
|
||||
if idPkTss[i].Ts > maxTs {
|
||||
maxTs = idPkTss[i].Ts
|
||||
}
|
||||
}
|
||||
if ev.CreatedAt < maxTs {
|
||||
err = errorf.E(
|
||||
"blocked: %0x was deleted by address %s because it is older than the delete: event: %d delete: %d",
|
||||
ev.ID, at, ev.CreatedAt, maxTs,
|
||||
)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise check for a delete by event id
|
||||
var idxs []database.Range
|
||||
if idxs, err = database.GetIndexesFromFilter(
|
||||
&filter.F{
|
||||
Authors: authors,
|
||||
Kinds: kind.NewS(kind.Deletion),
|
||||
Tags: tag.NewS(
|
||||
tag.NewFromAny("e", hexenc.Enc(ev.ID)),
|
||||
),
|
||||
},
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, idx := range idxs {
|
||||
var s types.Uint40s
|
||||
if s, err = w.GetSerialsByRange(idx); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if len(s) > 0 {
|
||||
// Any e-tag deletion found means the exact event was deleted
|
||||
err = errorf.E("blocked: %0x has been deleted", ev.ID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user