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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -461,57 +74,57 @@
- {
+ "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
}