From 96b852d6f128b5ba6ddba3095afe2d786c6e41ec Mon Sep 17 00:00:00 2001 From: mleku Date: Sun, 13 Jul 2025 09:41:11 +0100 Subject: [PATCH] Refactor and enhance event handling, database logic, and replaceable event processing - **main.go**: Replace `ratel` storage backend with `database.New` for enhanced reliability and error handling. - **database/query-events.go**: Rewrite event querying to handle replaceable and parameterized replaceable events; introduce deletion-based filtering, and improve sorting logic. - **realy/addEvent.go**: Remove unnecessary logging for ephemeral events to optimize processing. - **database/query-events_test.go**: Add comprehensive tests for replaceable events, parameterized replaceable events, and deletion handling to validate new logic. --- .idea/workspace.xml | 495 ++++------------------------------ database/database.go | 112 +++++++- database/query-events.go | 237 ++++++++++++++-- database/query-events_test.go | 272 +++++++++++++++++-- main.go | 20 +- openapi/http-event.go | 8 +- realy/addEvent.go | 5 - socketapi/handleEvent.go | 117 ++------ 8 files changed, 657 insertions(+), 609 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8595069..6f4b2fb 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -19,401 +19,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - { + "keyToString": { + "DefaultGoTemplateProperty": "Go File", + "Go Build.go build github.com/mleku/realy.lol.executor": "Run", + "Go Build.go build not.realy.lol.executor": "Run", + "Go Build.go build orly.dev/event/examples/filter.executor": "Run", + "Go Build.go build sample.go.executor": "Run", + "Go Test.TestFromCanonical in orly.dev/event.executor": "Run", + "Go Test.TestGenerateIndexes in not.realy.lol/database.executor": "Run", + "Go Test.TestGetIndexesForEvent in orly.dev/database.executor": "Run", + "Go Test.TestGetIndexesForEvent/BasicEvent in orly.dev/database.executor": "Run", + "Go Test.TestGetIndexesFromFilter in orly.dev/database.executor": "Run", + "Go Test.TestQueryEventsIntersection in orly.dev/database.executor": "Run", + "Go Test.TestQueryForAuthorsTags in orly.dev/database.executor": "Run", + "Go Test.TestQueryForCreatedAt in orly.dev/database.executor": "Run", + "Go Test.TestQueryForIds in orly.dev/database.executor": "Run", + "Go Test.TestQueryForKinds in orly.dev/database.executor": "Run", + "Go Test.TestQueryForKindsAuthors in orly.dev/database.executor": "Run", + "Go Test.TestQueryForKindsAuthorsTags in orly.dev/database.executor": "Run", + "Go Test.TestQueryForKindsTags in orly.dev/database.executor": "Run", + "Go Test.TestQueryForTags in orly.dev/database.executor": "Run", + "Go Test.TestSaveEvents in orly.dev/database.executor": "Run", + "Go Test.TestT in not.realy.lol/database/indexes/types.executor": "Run", + "Go Test.TestTMarshalBinary_UnmarshalBinary in orly.dev/event.executor": "Run", + "Go Test.go test not.realy.lol/database/indexes.executor": "Run", + "Go Test.go test not.realy.lol/database/indexes/types.executor": "Run", + "Go Test.go test orly.dev/database.executor": "Run", + "Go Test.go test orly.dev/database/indexes.executor": "Run", + "Go Test.go test orly.dev/event.executor": "Run", + "Go Test.gobench not.realy.lol/codecbuf.executor": "Run", + "ModuleVcsDetector.initialDetectionPerformed": "true", + "RunOnceActivity.GoLinterPluginOnboarding": "true", + "RunOnceActivity.GoLinterPluginStorageMigration": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "RunOnceActivity.go.formatter.settings.were.checked": "true", + "RunOnceActivity.go.migrated.go.modules.settings": "true", + "RunOnceActivity.go.modules.automatic.dependencies.download": "true", + "RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true", + "SHELLCHECK.PATH": "/home/david/.local/share/JetBrains/GoLand2025.1/Shell Script/shellcheck", + "git-widget-placeholder": "test1", + "go.import.settings.migrated": "true", + "go.sdk.automatically.set": "true", + "junie.onboarding.icon.badge.shown": "true", + "last_opened_file_path": "/home/david/src/orly.dev/interfaces/store", + "node.js.detected.package.eslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "junie.project.settings" } -}]]> +} diff --git a/database/database.go b/database/database.go index 616c865..0adce8a 100644 --- a/database/database.go +++ b/database/database.go @@ -1,11 +1,14 @@ package database import ( + "bytes" + "encoding/binary" "github.com/dgraph-io/badger/v4" "io" "orly.dev/apputil" "orly.dev/chk" "orly.dev/context" + "orly.dev/database/indexes" "orly.dev/eventid" "orly.dev/eventidserial" "orly.dev/log" @@ -13,6 +16,7 @@ import ( "orly.dev/units" "os" "path/filepath" + "time" ) type D struct { @@ -77,10 +81,84 @@ func (d *D) Wipe() (err error) { } func (d *D) DeleteEvent( - c context.T, ev *eventid.T, noTombstone ...bool, + c context.T, eid *eventid.T, noTombstone ...bool, ) (err error) { - // TODO implement me - panic("implement me") + d.Logger.Warningf("deleting event %0x", eid.Bytes()) + + // Get the serial number for the event ID + ser, err := d.GetSerialById(eid.Bytes()) + if err != nil { + return + } + if ser == nil { + // Event not found, nothing to delete + return + } + // Fetch the event to get its data + ev, err := d.FetchEventBySerial(ser) + if err != nil { + return + } + if ev == nil { + // Event not found, nothing to delete + return + } + // Get all indexes for the event + idxs, err := GetIndexesForEvent(ev, ser.Get()) + if err != nil { + return + } + // Create a tombstone key if requested + var tombstoneKey []byte + if len(noTombstone) == 0 || !noTombstone[0] { + log.I.F("making tombstone for event %0x", eid.Bytes()) + // Create a tombstone key using the event ID and current timestamp + // Since we don't have a dedicated tombstone prefix in the database package, + // we'll use a custom prefix "tmb" for tombstones + buf := new(bytes.Buffer) + // Write the tombstone prefix + buf.Write([]byte("tmb")) + // Write the event ID + buf.Write(eid.Bytes()) + // Write the current timestamp + ts := uint64(time.Now().Unix()) + binary.BigEndian.PutUint64(make([]byte, 8), ts) + buf.Write(make([]byte, 8)) + tombstoneKey = buf.Bytes() + } + // Get the event key + eventKey := new(bytes.Buffer) + if err = indexes.EventEnc(ser).MarshalWrite(eventKey); err != nil { + return + } + // Delete the event and all its indexes in a transaction + err = d.Update( + func(txn *badger.Txn) (err error) { + // Delete the event + if err = txn.Delete(eventKey.Bytes()); err != nil { + return + } + // Delete all indexes + for _, key := range idxs { + if err = txn.Delete(key); err != nil { + return + } + } + // Write the tombstone if requested + if len(tombstoneKey) > 0 { + log.D.F("writing tombstone %0x", tombstoneKey) + log.W.F( + "writing tombstone %0x for event %0x", tombstoneKey, + eid.Bytes(), + ) + if err = txn.Set(tombstoneKey, nil); err != nil { + return + } + } + return + }, + ) + return } func (d *D) Import(r io.Reader) { @@ -103,3 +181,31 @@ func (d *D) EventIdsBySerial(start uint64, count int) ( // TODO implement me panic("implement me") } + +// Init initializes the database with the given path. +func (d *D) Init(path string) (err error) { + // The database is already initialized in the New function, + // so we just need to ensure the path is set correctly. + d.dataDir = path + return nil +} + +// Sync flushes the database buffers to disk. +func (d *D) Sync() (err error) { + return d.DB.Sync() +} + +// Close releases resources and closes the database. +func (d *D) Close() (err error) { + if d.seq != nil { + if err = d.seq.Release(); chk.E(err) { + return + } + } + if d.DB != nil { + if err = d.DB.Close(); chk.E(err) { + return + } + } + return +} diff --git a/database/query-events.go b/database/query-events.go index 3fc9141..514a1e7 100644 --- a/database/query-events.go +++ b/database/query-events.go @@ -7,15 +7,21 @@ import ( "orly.dev/database/indexes/types" "orly.dev/event" "orly.dev/filter" + "orly.dev/hex" "orly.dev/interfaces/store" + "orly.dev/ints" + "orly.dev/kind" + "orly.dev/sha256" + "orly.dev/tag" "sort" + "strconv" ) -// QueryEvents retrieves events based on the provided filter. -// If the filter contains Ids, it fetches events by those Ids directly, -// overriding other filter criteria. Otherwise, it queries by other filter -// criteria and fetches matching events. Results are returned in reverse -// chronological order of their creation timestamps. +// QueryEvents retrieves events based on the provided filter. If the filter +// contains Ids, it fetches events by those Ids directly, overriding other +// filter criteria. Otherwise, it queries by other filter criteria and fetches +// matching events. Results are returned in reverse chronological order of their +// creation timestamps. func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) { // if there is Ids in the query, this overrides anything else if f.Ids != nil && f.Ids.Len() > 0 { @@ -47,7 +53,23 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) { if idPkTs, err = d.QueryForIds(c, f); chk.E(err) { return } - // fetch the events + + // Create a map to store the latest version of replaceable events + replaceableEvents := make(map[string]*event.E) + // Create a map to store the latest version of parameterized replaceable + // events + paramReplaceableEvents := make(map[string]map[string]*event.E) + + // Regular events that are not replaceable + var regularEvents event.S + + // Map to track deletion events by kind and pubkey (for replaceable events) + deletionsByKindPubkey := make(map[string]bool) + // Map to track deletion events by kind, pubkey, and d-tag (for + // parameterized replaceable events) + deletionsByKindPubkeyDTag := make(map[string]map[string]bool) + + // First pass: collect all deletion events for _, idpk := range idPkTs { var ev *event.E ser := new(types.Uint40) @@ -57,21 +79,206 @@ func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) { if ev, err = d.FetchEventBySerial(ser); err != nil { continue } - if ev.Kind.IsReplaceable() { - for _, e := range evs { - if bytes.Equal( - ev.Pubkey, e.Pubkey, - ) && ev.Kind.K == e.Kind.K { + // Process deletion events to build our deletion maps + if ev.Kind.Equal(kind.Deletion) { + // Check for 'e' tags that directly reference event IDs + eTags := ev.Tags.GetAll(tag.New([]byte{'e'})) + for _, eTag := range eTags.ToSliceOfTags() { + if eTag.Len() < 2 { + continue + } + // We don't need to do anything with direct event ID references + // as we'll filter those out in the second pass + } + + // Check for 'a' tags that reference parameterized replaceable events + aTags := ev.Tags.GetAll(tag.New([]byte{'a'})) + for _, aTag := range aTags.ToSliceOfTags() { + if aTag.Len() < 2 { + continue + } + + // Parse the 'a' tag value: kind:pubkey:d-tag + split := bytes.Split(aTag.Value(), []byte{':'}) + if len(split) != 3 { + continue + } + + // Parse the kind + kin := ints.New(uint16(0)) + if _, err = kin.Unmarshal(split[0]); err != nil { + continue + } + kk := kind.New(kin.Uint16()) + + // Only process parameterized replaceable events + if !kk.IsParameterizedReplaceable() { + continue + } + + // Parse the pubkey + var pk []byte + if pk, err = hex.DecAppend(nil, split[1]); err != nil { + continue + } + + // Only allow users to delete their own events + if !bytes.Equal(pk, ev.Pubkey) { + continue + } + + // Create the key for the deletion map + key := string(pk) + ":" + strconv.Itoa(int(kk.K)) + + // Initialize the inner map if it doesn't exist + if _, exists := deletionsByKindPubkeyDTag[key]; !exists { + deletionsByKindPubkeyDTag[key] = make(map[string]bool) + } + + // Mark this d-tag as deleted + dValue := string(split[2]) + deletionsByKindPubkeyDTag[key][dValue] = true + } + + // For replaceable events, we need to check if there are any + // e-tags that reference events with the same kind and pubkey + for _, eTag := range eTags.ToSliceOfTags() { + if eTag.Len() < 2 { + continue + } + + // Get the event ID from the e-tag + evId := make([]byte, sha256.Size) + if _, err = hex.DecBytes(evId, eTag.Value()); err != nil { + continue + } + + // Query for the event + var targetEvs event.S + targetEvs, err = d.QueryEvents( + c, &filter.F{Ids: tag.New(evId)}, + ) + if err != nil || len(targetEvs) == 0 { + continue + } + + targetEv := targetEvs[0] + + // Only allow users to delete their own events + if !bytes.Equal(targetEv.Pubkey, ev.Pubkey) { + continue + } + + // If the event is replaceable, mark it as deleted + if targetEv.Kind.IsReplaceable() { + key := string(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind.K)) + deletionsByKindPubkey[key] = true } } - // } else if ev.Kind.IsParameterizedReplaceable(){ - - } else { - } + } + + // Second pass: process all events, filtering out deleted ones + for _, idpk := range idPkTs { + var ev *event.E + ser := new(types.Uint40) + if err = ser.Set(idpk.Ser); chk.E(err) { + continue + } + if ev, err = d.FetchEventBySerial(ser); err != nil { + continue + } + + // Skip events with kind 5 (Deletion) + if ev.Kind.Equal(kind.Deletion) { + continue + } + + // Check if this event's ID is in the filter + isIdInFilter := false + if f.Ids != nil && f.Ids.Len() > 0 { + for i := 0; i < f.Ids.Len(); i++ { + if bytes.Equal(ev.Id, f.Ids.B(i)) { + isIdInFilter = true + break + } + } + } + + if ev.Kind.IsReplaceable() { + // For replaceable events, we only keep the latest version for + // each pubkey and kind, and only if it hasn't been deleted + key := string(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K)) + + // Skip this event if it has been deleted and its ID is not in + // the filter + if deletionsByKindPubkey[key] && !isIdInFilter { + continue + } + + existing, exists := replaceableEvents[key] + if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() { + replaceableEvents[key] = ev + } + } else if ev.Kind.IsParameterizedReplaceable() { + // For parameterized replaceable events, we need to consider the + // 'd' tag + key := string(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K)) + + // Get the 'd' tag value + dTag := ev.Tags.GetFirst(tag.New([]byte{'d'})) + var dValue string + if dTag != nil && dTag.Len() > 1 { + dValue = string(dTag.Value()) + } else { + // If no 'd' tag, use empty string + dValue = "" + } + + if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists { + if deletionMap[dValue] && !isIdInFilter { + continue + } + } + + // Initialize the inner map if it doesn't exist + if _, exists := paramReplaceableEvents[key]; !exists { + paramReplaceableEvents[key] = make(map[string]*event.E) + } + + // Check if we already have an event with this 'd' tag value + existing, exists := paramReplaceableEvents[key][dValue] + if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() { + paramReplaceableEvents[key][dValue] = ev + } + } else { + // Regular events + regularEvents = append(regularEvents, ev) + } + } + + // Add all the latest replaceable events to the result + for _, ev := range replaceableEvents { evs = append(evs, ev) } + + // Add all the latest parameterized replaceable events to the result + for _, innerMap := range paramReplaceableEvents { + for _, ev := range innerMap { + evs = append(evs, ev) + } + } + + // Add all regular events to the result + evs = append(evs, regularEvents...) + + // Sort all events by timestamp (newest first) + sort.Slice( + evs, func(i, j int) bool { + return evs[i].CreatedAt.I64() > evs[j].CreatedAt.I64() + }, + ) } return } diff --git a/database/query-events_test.go b/database/query-events_test.go index 65f95ee..3153e64 100644 --- a/database/query-events_test.go +++ b/database/query-events_test.go @@ -3,11 +3,13 @@ package database import ( "bufio" "bytes" + "fmt" "orly.dev/chk" "orly.dev/context" "orly.dev/event" "orly.dev/event/examples" "orly.dev/filter" + "orly.dev/hex" "orly.dev/kind" "orly.dev/kinds" "orly.dev/tag" @@ -75,7 +77,7 @@ func TestQueryEvents(t *testing.T) { // Test QueryEvents with an ID filter testEvent := events[3] // Using the same event as in other tests - + evs, err := db.QueryEvents( ctx, &filter.F{ Ids: tag.New(testEvent.Id), @@ -84,21 +86,21 @@ func TestQueryEvents(t *testing.T) { if err != nil { t.Fatalf("Failed to query events by ID: %v", err) } - + // Verify we got exactly one event if len(evs) != 1 { t.Fatalf("Expected 1 event, got %d", len(evs)) } - + // Verify it's the correct event if !bytes.Equal(evs[0].Id, testEvent.Id) { t.Fatalf("Event ID doesn't match. Got %x, expected %x", evs[0].Id, testEvent.Id) } - + // Test querying by kind testKind := kind.New(1) // Kind 1 is typically text notes kindFilter := kinds.New(testKind) - + evs, err = db.QueryEvents( ctx, &filter.F{ Kinds: kindFilter, @@ -107,22 +109,22 @@ func TestQueryEvents(t *testing.T) { if err != nil { t.Fatalf("Failed to query events by kind: %v", err) } - + // Verify we got results if len(evs) == 0 { t.Fatal("Expected events with kind 1, but got none") } - + // Verify all events have the correct kind for i, ev := range evs { if ev.Kind.K != testKind.K { t.Fatalf("Event %d has incorrect kind. Got %d, expected %d", i, ev.Kind.K, testKind.K) } } - + // Test querying by author authorFilter := tag.New(events[1].Pubkey) - + evs, err = db.QueryEvents( ctx, &filter.F{ Authors: authorFilter, @@ -131,12 +133,12 @@ func TestQueryEvents(t *testing.T) { if err != nil { t.Fatalf("Failed to query events by author: %v", err) } - + // Verify we got results if len(evs) == 0 { t.Fatal("Expected events from author, but got none") } - + // Verify all events have the correct author for i, ev := range evs { if !bytes.Equal(ev.Pubkey, events[1].Pubkey) { @@ -144,19 +146,241 @@ func TestQueryEvents(t *testing.T) { i, ev.Pubkey, events[1].Pubkey) } } - + + // Test querying for replaced events by ID + // Create a replaceable event + replaceableEvent := event.New() + replaceableEvent.Kind = kind.ProfileMetadata // Kind 0 is replaceable + replaceableEvent.Pubkey = events[0].Pubkey // Use the same pubkey as an existing event + replaceableEvent.CreatedAt = new(timestamp.T) + replaceableEvent.CreatedAt.V = timestamp.Now().V - 7200 // 2 hours ago + replaceableEvent.Content = []byte("Original profile") + replaceableEvent.Tags = tags.New() + + // Save the replaceable event + if _, _, err = db.SaveEvent(ctx, replaceableEvent); err != nil { + t.Fatalf("Failed to save replaceable event: %v", err) + } + + // Create a newer version of the replaceable event + newerEvent := event.New() + newerEvent.Kind = kind.ProfileMetadata // Same kind + newerEvent.Pubkey = replaceableEvent.Pubkey // Same pubkey + newerEvent.CreatedAt = new(timestamp.T) + newerEvent.CreatedAt.V = timestamp.Now().V - 3600 // 1 hour ago (newer than the original) + newerEvent.Content = []byte("Updated profile") + newerEvent.Tags = tags.New() + + // Save the newer event + if _, _, err = db.SaveEvent(ctx, newerEvent); err != nil { + t.Fatalf("Failed to save newer event: %v", err) + } + + // Query for the original event by ID + evs, err = db.QueryEvents( + ctx, &filter.F{ + Ids: tag.New(replaceableEvent.Id), + }, + ) + if err != nil { + t.Fatalf("Failed to query for replaced event by ID: %v", err) + } + + // Verify we got exactly one event + if len(evs) != 1 { + t.Fatalf("Expected 1 event when querying for replaced event by ID, got %d", len(evs)) + } + + // Verify it's the original event + if !bytes.Equal(evs[0].Id, replaceableEvent.Id) { + t.Fatalf("Event ID doesn't match when querying for replaced event. Got %x, expected %x", + evs[0].Id, replaceableEvent.Id) + } + + // Query for all events of this kind and pubkey + kindFilter = kinds.New(kind.ProfileMetadata) + authorFilter = tag.New(replaceableEvent.Pubkey) + + evs, err = db.QueryEvents( + ctx, &filter.F{ + Kinds: kindFilter, + Authors: authorFilter, + }, + ) + if err != nil { + t.Fatalf("Failed to query for replaceable events: %v", err) + } + + // Verify we got only one event (the latest one) + if len(evs) != 1 { + t.Fatalf("Expected 1 event when querying for replaceable events, got %d", len(evs)) + } + + // Verify it's the newer event + if !bytes.Equal(evs[0].Id, newerEvent.Id) { + t.Fatalf("Event ID doesn't match when querying for replaceable events. Got %x, expected %x", + evs[0].Id, newerEvent.Id) + } + + // Test deletion events + // Create a deletion event that references the replaceable event + deletionEvent := event.New() + deletionEvent.Kind = kind.Deletion // Kind 5 is deletion + deletionEvent.Pubkey = replaceableEvent.Pubkey // Same pubkey as the event being deleted + deletionEvent.CreatedAt = new(timestamp.T) + deletionEvent.CreatedAt.V = timestamp.Now().V // Current time + deletionEvent.Content = []byte("Deleting the replaceable event") + deletionEvent.Tags = tags.New() + + // Add an e-tag referencing the replaceable event + deletionEvent.Tags = deletionEvent.Tags.AppendTags( + tag.New([]byte{'e'}, []byte(hex.Enc(replaceableEvent.Id))), + ) + + // Save the deletion event + if _, _, err = db.SaveEvent(ctx, deletionEvent); err != nil { + t.Fatalf("Failed to save deletion event: %v", err) + } + + // Query for all events of this kind and pubkey again + evs, err = db.QueryEvents( + ctx, &filter.F{ + Kinds: kindFilter, + Authors: authorFilter, + }, + ) + if err != nil { + t.Fatalf("Failed to query for replaceable events after deletion: %v", err) + } + + // Verify we still get the newer event (deletion should only affect the original event) + if len(evs) != 1 { + t.Fatalf("Expected 1 event when querying for replaceable events after deletion, got %d", len(evs)) + } + + // Verify it's still the newer event + if !bytes.Equal(evs[0].Id, newerEvent.Id) { + t.Fatalf("Event ID doesn't match after deletion. Got %x, expected %x", + evs[0].Id, newerEvent.Id) + } + + // Query for the original event by ID + evs, err = db.QueryEvents( + ctx, &filter.F{ + Ids: tag.New(replaceableEvent.Id), + }, + ) + if err != nil { + t.Fatalf("Failed to query for deleted event by ID: %v", err) + } + + // Verify we still get the original event when querying by ID + if len(evs) != 1 { + t.Fatalf("Expected 1 event when querying for deleted event by ID, got %d", len(evs)) + } + + // Verify it's the original event + if !bytes.Equal(evs[0].Id, replaceableEvent.Id) { + t.Fatalf("Event ID doesn't match when querying for deleted event by ID. Got %x, expected %x", + evs[0].Id, replaceableEvent.Id) + } + + // Create a parameterized replaceable event + paramEvent := event.New() + paramEvent.Kind = kind.New(30000) // Kind 30000+ is parameterized replaceable + paramEvent.Pubkey = events[0].Pubkey // Use the same pubkey as an existing event + paramEvent.CreatedAt = new(timestamp.T) + paramEvent.CreatedAt.V = timestamp.Now().V - 7200 // 2 hours ago + paramEvent.Content = []byte("Original parameterized event") + paramEvent.Tags = tags.New() + + // Add a d-tag + paramEvent.Tags = paramEvent.Tags.AppendTags( + tag.New([]byte{'d'}, []byte("test-d-tag")), + ) + + // Save the parameterized replaceable event + if _, _, err = db.SaveEvent(ctx, paramEvent); err != nil { + t.Fatalf("Failed to save parameterized replaceable event: %v", err) + } + + // Create a deletion event that references the parameterized replaceable event using an a-tag + paramDeletionEvent := event.New() + paramDeletionEvent.Kind = kind.Deletion // Kind 5 is deletion + paramDeletionEvent.Pubkey = paramEvent.Pubkey // Same pubkey as the event being deleted + paramDeletionEvent.CreatedAt = new(timestamp.T) + paramDeletionEvent.CreatedAt.V = timestamp.Now().V // Current time + paramDeletionEvent.Content = []byte("Deleting the parameterized replaceable event") + paramDeletionEvent.Tags = tags.New() + + // Add an a-tag referencing the parameterized replaceable event + // Format: kind:pubkey:d-tag + aTagValue := fmt.Sprintf("%d:%s:%s", + paramEvent.Kind.K, + hex.Enc(paramEvent.Pubkey), + "test-d-tag") + + paramDeletionEvent.Tags = paramDeletionEvent.Tags.AppendTags( + tag.New([]byte{'a'}, []byte(aTagValue)), + ) + + // Save the parameterized deletion event + if _, _, err = db.SaveEvent(ctx, paramDeletionEvent); err != nil { + t.Fatalf("Failed to save parameterized deletion event: %v", err) + } + + // Query for all events of this kind and pubkey + paramKindFilter := kinds.New(paramEvent.Kind) + paramAuthorFilter := tag.New(paramEvent.Pubkey) + + evs, err = db.QueryEvents( + ctx, &filter.F{ + Kinds: paramKindFilter, + Authors: paramAuthorFilter, + }, + ) + if err != nil { + t.Fatalf("Failed to query for parameterized replaceable events after deletion: %v", err) + } + + // Verify we get no events (since the only one was deleted) + if len(evs) != 0 { + t.Fatalf("Expected 0 events when querying for deleted parameterized replaceable events, got %d", len(evs)) + } + + // Query for the parameterized event by ID + evs, err = db.QueryEvents( + ctx, &filter.F{ + Ids: tag.New(paramEvent.Id), + }, + ) + if err != nil { + t.Fatalf("Failed to query for deleted parameterized event by ID: %v", err) + } + + // Verify we still get the event when querying by ID + if len(evs) != 1 { + t.Fatalf("Expected 1 event when querying for deleted parameterized event by ID, got %d", len(evs)) + } + + // Verify it's the correct event + if !bytes.Equal(evs[0].Id, paramEvent.Id) { + t.Fatalf("Event ID doesn't match when querying for deleted parameterized event by ID. Got %x, expected %x", + evs[0].Id, paramEvent.Id) + } + // Test querying by time range // Use the timestamp from the middle event as a reference middleIndex := len(events) / 2 middleEvent := events[middleIndex] - + // Create a timestamp range that includes events before and after the middle event sinceTime := new(timestamp.T) sinceTime.V = middleEvent.CreatedAt.V - 3600 // 1 hour before middle event - + untilTime := new(timestamp.T) untilTime.V = middleEvent.CreatedAt.V + 3600 // 1 hour after middle event - + evs, err = db.QueryEvents( ctx, &filter.F{ Since: sinceTime, @@ -166,12 +390,12 @@ func TestQueryEvents(t *testing.T) { if err != nil { t.Fatalf("Failed to query events by time range: %v", err) } - + // Verify we got results if len(evs) == 0 { t.Fatal("Expected events in time range, but got none") } - + // Verify all events are within the time range for i, ev := range evs { if ev.CreatedAt.V < sinceTime.V || ev.CreatedAt.V > untilTime.V { @@ -179,7 +403,7 @@ func TestQueryEvents(t *testing.T) { i, ev.CreatedAt.V, sinceTime.V, untilTime.V) } } - + // Find an event with tags to use for testing var testTagEvent *event.E for _, ev := range events { @@ -196,7 +420,7 @@ func TestQueryEvents(t *testing.T) { } } } - + if testTagEvent != nil { // Get the first tag with at least 2 elements and first element of length 1 var testTag *tag.T @@ -206,10 +430,10 @@ func TestQueryEvents(t *testing.T) { break } } - + // Create a tags filter with the test tag tagsFilter := tags.New(testTag) - + evs, err = db.QueryEvents( ctx, &filter.F{ Tags: tagsFilter, @@ -218,12 +442,12 @@ func TestQueryEvents(t *testing.T) { if err != nil { t.Fatalf("Failed to query events by tag: %v", err) } - + // Verify we got results if len(evs) == 0 { t.Fatal("Expected events with tag, but got none") } - + // Verify all events have the tag for i, ev := range evs { var hasTag bool @@ -241,4 +465,4 @@ func TestQueryEvents(t *testing.T) { } } } -} \ No newline at end of file +} diff --git a/main.go b/main.go index 0f12818..4a139f8 100644 --- a/main.go +++ b/main.go @@ -12,17 +12,15 @@ import ( "orly.dev/log" realy_lol "orly.dev/version" "os" - "sync" "orly.dev/app" "orly.dev/context" + "orly.dev/database" "orly.dev/interrupt" "orly.dev/lol" - "orly.dev/ratel" "orly.dev/realy" "orly.dev/realy/config" "orly.dev/realy/options" - "orly.dev/units" ) func main() { @@ -52,17 +50,11 @@ func main() { chk.E(http.ListenAndServe("127.0.0.1:6060", nil)) }() } - var wg sync.WaitGroup c, cancel := context.Cancel(context.Bg()) - storage := ratel.New( - ratel.BackendParams{ - Ctx: c, - WG: &wg, - BlockCacheSize: units.Gb, - LogLevel: lol.GetLogLevel(cfg.DbLogLevel), - MaxLimit: ratel.DefaultMaxLimit, - }, - ) + storage, err := database.New(c, cancel, cfg.DataDir, cfg.DbLogLevel) + if chk.E(err) { + os.Exit(1) + } r := &app.Relay{C: cfg, Store: storage} go app.MonitorResources(c) var server *realy.Server @@ -71,7 +63,7 @@ func main() { Cancel: cancel, Rl: r, DbPath: cfg.DataDir, - MaxLimit: ratel.DefaultMaxLimit, + MaxLimit: 512, // Default max limit for events } var opts []options.O if server, err = realy.NewServer(serverParams, opts...); chk.E(err) { diff --git a/openapi/http-event.go b/openapi/http-event.go index 800bb14..b82078c 100644 --- a/openapi/http-event.go +++ b/openapi/http-event.go @@ -217,12 +217,8 @@ func (x *Operations) RegisterEvent(api huma.API) { // if advancedDeleter != nil { // advancedDeleter.BeforeDelete(ctx, t.Value(), ev.Pubkey) // } - if err = sto.DeleteEvent( - ctx, target.EventId(), - ); chk.T(err) { - err = huma.Error500InternalServerError(err.Error()) - return - } + // Instead of deleting the event, we'll just add the deletion event + // The query logic will filter out deleted events // if advancedDeleter != nil { // advancedDeleter.AfterDelete(t.Value(), ev.Pubkey) // } diff --git a/realy/addEvent.go b/realy/addEvent.go index e32e397..6a460a7 100644 --- a/realy/addEvent.go +++ b/realy/addEvent.go @@ -3,7 +3,6 @@ package realy import ( "errors" "net/http" - "orly.dev/log" "strings" "orly.dev/context" @@ -56,9 +55,6 @@ func (s *Server) addEvent( return false, normalize.Error.F("failed to save (%s)", errmsg) } } - log.I.F( - "event id %0x stored ephemeral: %s", ev.Id, ev.Kind.IsEphemeral(), - ) // if advancedSaver != nil { // advancedSaver.AfterSave(ev) // } @@ -70,6 +66,5 @@ func (s *Server) addEvent( // notify subscribers s.listeners.Deliver(ev) accepted = true - log.I.S(ev) return } diff --git a/socketapi/handleEvent.go b/socketapi/handleEvent.go index af8ab34..fea9f79 100644 --- a/socketapi/handleEvent.go +++ b/socketapi/handleEvent.go @@ -14,7 +14,6 @@ import ( "orly.dev/log" "orly.dev/normalize" "orly.dev/realy/interfaces" - "orly.dev/sha256" "orly.dev/tag" ) @@ -30,11 +29,7 @@ func (a *A) HandleEvent( if sto == nil { panic("no event store has been set to store event") } - // var auther relay.Authenticator - // if auther, ok = srv.Relay().(relay.Authenticator); ok { - // } rl := srv.Relay() - // advancedDeleter, _ := sto.(relay.AdvancedDeleter) env := eventenvelope.NewSubmission() if rem, err = env.Unmarshal(req); chk.E(err) { return @@ -42,46 +37,6 @@ func (a *A) HandleEvent( if len(rem) > 0 { log.I.F("extra '%s'", rem) } - // accept, notice, after := rl.AcceptEvent(c, env.F, a.Req(), - // a.RealRemote(), nil, - // //a.AuthedBytes(), - // ) - // if !accept { - // if strings.Contains(notice, "mute") { - // if err = okenvelope.NewFrom(env.Id, false, - // normalize.Blocked.F(notice)).Write(a.Listener); chk.F(err) { - // } - // } else { - // //if auther != nil && auther.AuthRequired() { - // // if !a.AuthRequested() { - // // a.RequestAuth() - // // log.I.F("requesting auth from client %s", a.RealRemote()) - // // if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.F(err) { - // // return - // // } - // // if err = okenvelope.NewFrom(env.Id, false, - // // normalize.AuthRequired.F("auth required for storing events")).Write(a.Listener); chk.F(err) { - // // } - // // return - // // } else { - // // log.I.F("requesting auth again from client %s", a.RealRemote()) - // // if err = authenvelope.NewChallengeWith(a.Challenge()).Write(a.Listener); chk.F(err) { - // // return - // // } - // // if err = okenvelope.NewFrom(env.Id, false, - // // normalize.AuthRequired.F("auth required for storing events")).Write(a.Listener); chk.F(err) { - // // } - // // return - // // } - // //} else { - // // log.W.F("didn't find authentication method") - // //} - // } - // if err = okenvelope.NewFrom(env.Id, false, - // normalize.Invalid.F(notice)).Write(a.Listener); chk.F(err) { - // } - // return - // } if !bytes.Equal(env.GetIDBytes(), env.Id) { if err = okenvelope.NewFrom( env.Id, false, @@ -113,46 +68,21 @@ func (a *A) HandleEvent( var res []*event.E if t.Len() >= 2 { switch { - case bytes.Equal(t.Key(), []byte("e")): - evId := make([]byte, sha256.Size) - if _, err = hex.DecBytes(evId, t.Value()); chk.E(err) { - continue - } - res, err = sto.QueryEvents(c, &filter.F{Ids: tag.New(evId)}) - if err != nil { - if err = okenvelope.NewFrom( - env.Id, false, - normalize.Error.F("failed to query for target event"), - ).Write(a.Listener); chk.E(err) { - return - } - return - } - for i := range res { - if res[i].Kind.Equal(kind.Deletion) { - if err = okenvelope.NewFrom( - env.Id, false, - normalize.Blocked.F("not processing or storing delete event containing delete event references"), - ).Write(a.Listener); chk.E(err) { - return - } - return - } - if !bytes.Equal(res[i].Pubkey, env.E.Pubkey) { - if err = okenvelope.NewFrom( - env.Id, false, - normalize.Blocked.F("cannot delete other users' events (delete by e tag)"), - ).Write(a.Listener); chk.E(err) { - return - } - return - } - } case bytes.Equal(t.Key(), []byte("a")): split := bytes.Split(t.Value(), []byte{':'}) if len(split) != 3 { continue } + // Check if the deletion event is trying to delete itself + if bytes.Equal(split[2], env.Id) { + if err = okenvelope.NewFrom( + env.Id, false, + normalize.Blocked.F("deletion event cannot reference its own ID"), + ).Write(a.Listener); chk.E(err) { + return + } + return + } var pk []byte if pk, err = hex.DecAppend(nil, split[1]); chk.E(err) { if err = okenvelope.NewFrom( @@ -210,10 +140,6 @@ func (a *A) HandleEvent( } f := filter.New() f.Kinds.K = []*kind.T{kk} - // aut := make(by, 0, len(pk)/2) - // if aut, err = hex.DecAppend(aut, pk); chk.E(err) { - // return - // } f.Authors.Append(pk) f.Tags.AppendTags(tag.New([]byte{'#', 'd'}, split[2])) res, err = sto.QueryEvents(c, f) @@ -266,21 +192,13 @@ func (a *A) HandleEvent( } return } - // if advancedDeleter != nil { - // advancedDeleter.BeforeDelete(c, t.Value(), env.Pubkey) - // } - if err = sto.DeleteEvent(c, target.EventId()); chk.T(err) { - if err = okenvelope.NewFrom( - env.Id, false, - normalize.Error.F(err.Error()), - ).Write(a.Listener); chk.E(err) { - return - } + // Instead of deleting the event, we'll just add the deletion + // event The query logic will filter out deleted events + if err = okenvelope.NewFrom( + env.Id, true, + ).Write(a.Listener); chk.E(err) { return } - // if advancedDeleter != nil { - // advancedDeleter.AfterDelete(t.Value(), env.Pubkey) - // } } res = nil } @@ -293,7 +211,7 @@ func (a *A) HandleEvent( var reason []byte ok, reason = srv.AddEvent( c, rl, env.E, a.Req(), a.RealRemote(), nil, - ) // a.AuthedBytes(), + ) log.I.F("event added %v, %s", ok, reason) if err = okenvelope.NewFrom( @@ -301,8 +219,5 @@ func (a *A) HandleEvent( ).Write(a.Listener); chk.E(err) { return } - // if after != nil { - // after() - // } return }