From b931109ac88f55987ac8c6c2097ffb6a67d4b280 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Wed, 11 Feb 2026 23:23:17 -0600 Subject: [PATCH] Code polish, minor refactors, and type hints --- lib/gc_index_relay/nostr.ex | 8 +- lib/gc_index_relay/nostr/event.ex | 2 - lib/gc_index_relay/nostr/filter.ex | 6 +- lib/gc_index_relay/nostr/pub_event.ex | 84 +++++++++++-------- .../controllers/event_controller.ex | 8 +- 5 files changed, 56 insertions(+), 52 deletions(-) diff --git a/lib/gc_index_relay/nostr.ex b/lib/gc_index_relay/nostr.ex index 4dc39ce..edd257b 100644 --- a/lib/gc_index_relay/nostr.ex +++ b/lib/gc_index_relay/nostr.ex @@ -5,7 +5,7 @@ defmodule GcIndexRelay.Nostr do ## Supported Operations The Nostr context module supports create, read, and delete operations on events, as well as a - number of query types to find collections of events. Update operations are not supported, sincafter + number of query types to find collections of events. Update operations are not supported, since a Nostr event, once signed, is immutable. """ @@ -20,7 +20,8 @@ defmodule GcIndexRelay.Nostr do Returns: `GcIndexRelay.Nostr.PubEvent` """ - def get_event!(id) when is_binary(id) do + @spec get_event(binary()) :: {:ok, PubEvent.t()} | {:error, :not_found} + def get_event(id) when is_binary(id) do Event |> Repo.get(id) |> Repo.preload(:tags) @@ -38,14 +39,13 @@ defmodule GcIndexRelay.Nostr do %Event{} |> Event.changeset(Map.from_struct(db_event)) |> Repo.insert() - else - {:error, reason} -> {:error, reason} end end @doc """ Deletes a `GcIndexRelay.Nostr.PubEvent` from the database. """ + @spec delete_event(PubEvent.t()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} def delete_event(event) when is_struct(event, PubEvent) do db_event = PubEvent.to_db(event) diff --git a/lib/gc_index_relay/nostr/event.ex b/lib/gc_index_relay/nostr/event.ex index 5fef294..934615c 100644 --- a/lib/gc_index_relay/nostr/event.ex +++ b/lib/gc_index_relay/nostr/event.ex @@ -21,12 +21,10 @@ defmodule GcIndexRelay.Nostr.Event do @primary_key {:id, :binary_id, autogenerate: false} schema "events" do - # 32-bytes lowercase hex-encoded field :pubkey, :binary field :created_at, :utc_datetime field :kind, :integer field :content, :string - # 64 bytes lowercase hex-encoded field :sig, :binary has_many :tags, GcIndexRelay.Nostr.Tag end diff --git a/lib/gc_index_relay/nostr/filter.ex b/lib/gc_index_relay/nostr/filter.ex index 685e800..5b6e04a 100644 --- a/lib/gc_index_relay/nostr/filter.ex +++ b/lib/gc_index_relay/nostr/filter.ex @@ -46,8 +46,6 @@ defmodule GcIndexRelay.Nostr.Filter do until: until, limit: limit }} - else - {:error, reason} -> {:error, reason} end end @@ -139,8 +137,6 @@ defmodule GcIndexRelay.Nostr.Filter do with :ok <- validate_tag_keys(tags), :ok <- validate_tag_values(tags) do {:ok, tags} - else - {:error, reason} -> {:error, reason} end end @@ -257,7 +253,7 @@ defmodule GcIndexRelay.Nostr.Filter do @spec apply_authors(Ecto.Query.t(), [String.t()] | nil) :: Ecto.Query.t() defp apply_authors(query, nil), do: query defp apply_authors(query, []), do: query - defp apply_authors(query, authors), do: where(query, [e], e.author in ^authors) + defp apply_authors(query, authors), do: where(query, [e], e.pubkey in ^authors) @spec apply_kinds(Ecto.Query.t(), [integer()] | nil) :: Ecto.Query.t() defp apply_kinds(query, nil), do: query diff --git a/lib/gc_index_relay/nostr/pub_event.ex b/lib/gc_index_relay/nostr/pub_event.ex index c2d82a7..5a261e6 100644 --- a/lib/gc_index_relay/nostr/pub_event.ex +++ b/lib/gc_index_relay/nostr/pub_event.ex @@ -12,57 +12,72 @@ defmodule GcIndexRelay.Nostr.PubEvent do @derive Jason.Encoder defstruct [:id, :pubkey, :created_at, :kind, :tags, :content, :sig] + @type t :: %__MODULE__{ + id: binary(), + pubkey: binary(), + created_at: integer(), + kind: integer(), + tags: [[String.t()]], + content: String.t(), + sig: binary() + } + @doc """ Converts a `GcIndexRelay.Nostr.PubEvent` to its corresponding `GcIndexRelay.Nostr.Event` and `GcIndexRelay.Nostr.Tag` representations. - Returns an Ecto changeset for `GcIndexRelay.Nostr.Event`. + Returns an Ecto for `GcIndexRelay.Nostr.Event`. """ + @spec to_db(t()) :: Ecto.Schema.t() def to_db(%__MODULE__{tags: tags} = pub_event) do %Event{to_event(pub_event) | tags: to_tags(tags)} end - defp to_event(%__MODULE__{ - id: id, - pubkey: pubkey, - created_at: epoch, - kind: kind, - content: content, - sig: signature - }) - when is_binary(id) and is_binary(pubkey) and is_integer(epoch) and is_integer(kind) and - is_binary(signature) do - %Event{ - id: Base.decode16(id, case: :lower), - pubkey: Base.decode16(pubkey, case: :lower), - created_at: DateTime.from_unix!(epoch), - kind: kind, - content: content, - sig: Base.decode16(signature, case: :lower) - } + @spec to_event(t()) :: Ecto.Schema.t() + defp to_event(%__MODULE__{} = pub_event) + when is_binary(pub_event.id) and is_binary(pub_event.pubkey) and + is_integer(pub_event.created_at) and is_integer(pub_event.kind) and + is_binary(pub_event.sig) do + with {:ok, id} <- Base.decode16(pub_event.id, case: :lower), + {:ok, pubkey} <- Base.decode16(pub_event.pubkey, case: :lower), + {:ok, signature} <- Base.decode16(pub_event.sig, case: :lower) do + %Event{ + id: id, + pubkey: pubkey, + created_at: DateTime.from_unix!(pub_event.created_at), + kind: pub_event.kind, + content: pub_event.content, + sig: signature + } + else + error -> {:error, error} + end end + @spec to_tags([[String.t()]]) :: [Ecto.Schema.t()] defp to_tags(tags) when is_list(tags) do - for t <- tags, do: to_tag(t) - end - - defp to_tag(tag) when is_list(tag) do - [name | values] = tag - [value | rest] = values + for t <- tags do + [name | values] = t + # Single-element tags will cause a crash + [value | rest] = values - %Tag{ - name: name, - value: value, - additional_values: rest - } + %Tag{ + name: name, + value: value, + additional_values: rest + } + end end @doc """ Converts the DB representations of `GcIndexRelay.Nostr.Event` and `GcIndexRelay.Nostr.Tag` to the domain representation `GcIndexRelay.Nostr.PubEvent`. """ + @spec from_db(struct()) :: {:ok, t()} | {:error, :not_found} + def from_db(event) when is_nil(event), do: {:error, :not_found} + def from_db(%Event{tags: tags} = event) when is_struct(event, Event) and is_list(tags) do - %{from_event(event) | tags: from_tags(tags)} + {:ok, %{from_event(event) | tags: from_tags(tags)}} end defp from_event(%Event{} = event) when is_struct(event, Event) do @@ -77,11 +92,6 @@ defmodule GcIndexRelay.Nostr.PubEvent do end defp from_tags(tags) when is_list(tags) do - for t <- tags, do: from_tag(t) - end - - defp from_tag(%Tag{name: name, value: value, additional_values: rest}) - when is_binary(name) and is_binary(value) and is_list(rest) do - [name | [value | rest]] + for t <- tags, do: [t.name, t.value | t.additional_values] end end diff --git a/lib/gc_index_relay_web/controllers/event_controller.ex b/lib/gc_index_relay_web/controllers/event_controller.ex index cb6e25f..a7eb787 100644 --- a/lib/gc_index_relay_web/controllers/event_controller.ex +++ b/lib/gc_index_relay_web/controllers/event_controller.ex @@ -3,6 +3,7 @@ defmodule GcIndexRelayWeb.EventController do alias GcIndexRelay.Nostr alias GcIndexRelay.Nostr.Event + alias GcIndexRelay.Nostr.PubEvent action_fallback GcIndexRelayWeb.FallbackController @@ -18,14 +19,13 @@ defmodule GcIndexRelayWeb.EventController do def show(conn, %{"id" => id}) do # TODO: Add 404 for event not found in FallbackController - event = Nostr.get_event!(id) + event = Nostr.get_event(id) render(conn, :show, event: event) end def delete(conn, %{"id" => id}) do - event = Nostr.get_event!(id) - - with {:ok, %Event{}} <- Nostr.delete_event(event) do + with {:ok, %PubEvent{} = pub_event} <- Nostr.get_event(id), + {:ok, %Event{} = _} <- Nostr.delete_event(pub_event) do send_resp(conn, :no_content, "") end end