diff --git a/lib/gc_index_relay/nostr/event.ex b/lib/gc_index_relay/nostr/event.ex index 7a590fa..a4cc053 100644 --- a/lib/gc_index_relay/nostr/event.ex +++ b/lib/gc_index_relay/nostr/event.ex @@ -26,7 +26,7 @@ defmodule GcIndexRelay.Nostr.Event do field :kind, :integer field :content, :string field :sig, :binary - has_many :tags, GcIndexRelay.Nostr.Tag + has_many :tags, GcIndexRelay.Nostr.Tag, preload_order: [asc: :id] end @doc false diff --git a/lib/gc_index_relay/nostr/pub_event.ex b/lib/gc_index_relay/nostr/pub_event.ex index 4b12a9a..d5d271c 100644 --- a/lib/gc_index_relay/nostr/pub_event.ex +++ b/lib/gc_index_relay/nostr/pub_event.ex @@ -103,11 +103,21 @@ defmodule GcIndexRelay.Nostr.PubEvent do end defp from_tags(tags) when is_list(tags) do - for t <- tags do + tags + |> sort_tags_for_read() + |> Enum.map(fn t -> case t.value do nil -> [t.name] value -> [t.name, value | t.additional_values] end - end + end) + end + + # Tag rows have no explicit position column; insertion id matches Nostr signing order. + defp sort_tags_for_read(tags) do + Enum.sort_by(tags, fn + %Tag{id: id} when is_integer(id) -> {0, id} + %Tag{} -> {1, 0} + end) end end diff --git a/test/gc_index_relay/nostr/pub_event_test.exs b/test/gc_index_relay/nostr/pub_event_test.exs index 893b894..1421184 100644 --- a/test/gc_index_relay/nostr/pub_event_test.exs +++ b/test/gc_index_relay/nostr/pub_event_test.exs @@ -59,6 +59,25 @@ defmodule GcIndexRelay.Nostr.PubEventTest do assert {:error, :not_found} = PubEvent.from_db(nil) end + test "restores Nostr tag order from shuffled tag rows by database id" do + event = %Event{ + id: Base.decode16!(String.duplicate("ab", 32), case: :lower), + pubkey: Base.decode16!(String.duplicate("cd", 32), case: :lower), + created_at: ~U[2021-12-20 17:46:40Z], + kind: 30_040, + content: "", + sig: Base.decode16!(String.duplicate("ef", 64), case: :lower), + tags: [ + %Tag{id: 300, name: "a", value: "chapter-2", additional_values: []}, + %Tag{id: 100, name: "d", value: "book", additional_values: []}, + %Tag{id: 200, name: "title", value: "Title", additional_values: []} + ] + } + + assert {:ok, pub_event} = PubEvent.from_db(event) + assert pub_event.tags == [["d", "book"], ["title", "Title"], ["a", "chapter-2"]] + end + test "converts Tag structs back to nested lists" do event = %Event{ id: Base.decode16!(String.duplicate("ab", 32), case: :lower), diff --git a/test/gc_index_relay/nostr/publication_search_test.exs b/test/gc_index_relay/nostr/publication_search_test.exs index 26cf17e..28763ca 100644 --- a/test/gc_index_relay/nostr/publication_search_test.exs +++ b/test/gc_index_relay/nostr/publication_search_test.exs @@ -5,6 +5,7 @@ defmodule GcIndexRelay.Nostr.PublicationSearchTest do alias GcIndexRelay.Nostr alias GcIndexRelay.Nostr.PublicationSearch + alias GcIndexRelay.Nostr.Validator @moduletag :integration @@ -62,6 +63,19 @@ defmodule GcIndexRelay.Nostr.PublicationSearchTest do assert length(results) == 1 end + test "search returns signature-valid events" do + insert_publication!( + "pg1342-pride-and-prejudice", + "Pride and Prejudice", + "Jane Austen", + "https://www.gutenberg.org/ebooks/1342" + ) + + assert {:ok, [result | _]} = PublicationSearch.search("pride and prejudice", limit: 10) + assert {:ok, ^result} = Validator.validate_signature(result) + assert {:ok, ^result} = Validator.validate_id(result) + end + test "search rejects partial substring matches" do insert_publication!( "pg1342-pride-and-prejudice",