dev vouch dev on at. thats about it atvouch.dev
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

add getEntireGraph

Luna 7a605f41 35d9d5fe

+192 -5
+4 -5
appview/config/test.exs
··· 1 1 import Config 2 2 3 3 config :atvouch, 4 - port: 4002, 4 + port: 4057, 5 5 atvouch_did: "did:plc:test", 6 - atvouch_endpoint: "https://localhost:4002" 6 + atvouch_endpoint: "https://localhost:123" 7 7 8 - config :atvouch, Atvouch.Repo, 9 - pool: Ecto.Adapters.SQL.Sandbox 8 + config :atvouch, Atvouch.Repo, pool: Ecto.Adapters.SQL.Sandbox 10 9 11 10 config :atvouch, :tap, 12 - uri: "ws://localhost:0/channel", 11 + uri: "ws://localhost:123/channel", 13 12 password: "123" 14 13 15 14 config :logger,
+11
appview/lib/atvouch/lexicons/get_entire_graph.ex
··· 1 + defmodule Atvouch.Lexicons.Graph.GetEntireGraph do 2 + use Atex.Lexicon 3 + 4 + deflexicon( 5 + Jason.decode!( 6 + File.read!( 7 + Path.join([File.cwd!(), "..", "lexicons", "dev", "atvouch", "graph", "getEntireGraph.json"]) 8 + ) 9 + ) 10 + ) 11 + end
+7
appview/lib/atvouch/vouch.ex
··· 56 56 from(v in __MODULE__, where: v.target_did == ^did) 57 57 |> Atvouch.Repo.all() 58 58 end 59 + 60 + def paginated(limit, offset) do 61 + import Ecto.Query 62 + 63 + from(v in __MODULE__, order_by: [asc: v.at_uri], limit: ^limit, offset: ^offset) 64 + |> Atvouch.Repo.all() 65 + end 59 66 end
+47
appview/lib/atvouch/xrpc_router.ex
··· 13 13 14 14 alias Atvouch.Lexicons.Graph.Defs 15 15 alias Atvouch.Lexicons.Graph.GetCurrentUserVouches 16 + alias Atvouch.Lexicons.Graph.GetEntireGraph 16 17 alias Atvouch.Lexicons.Graph.GetRemoteVouches 17 18 18 19 get "/dev.atvouch.graph.getCurrentUserVouches" do ··· 45 46 end 46 47 end 47 48 49 + get "/dev.atvouch.graph.getEntireGraph" do 50 + conn = fetch_query_params(conn) 51 + params = conn.query_params 52 + 53 + with {:ok, limit} <- parse_limit(params), 54 + {:ok, offset} <- parse_cursor(params) do 55 + vouches = 56 + Atvouch.Vouch.paginated(limit, offset) 57 + |> Enum.map(&to_vouch_view/1) 58 + 59 + next_cursor = 60 + if length(vouches) == limit do 61 + Integer.to_string(offset + limit) 62 + end 63 + 64 + output = %GetEntireGraph.Output{vouches: vouches, cursor: next_cursor} 65 + 66 + conn 67 + |> put_resp_content_type("application/json") 68 + |> send_resp(200, Jason.encode!(output)) 69 + else 70 + {:error, message} -> 71 + conn 72 + |> put_resp_content_type("application/json") 73 + |> send_resp(400, Jason.encode!(%{error: "InvalidRequest", message: message})) 74 + end 75 + end 76 + 48 77 match _ do 49 78 conn 50 79 |> put_resp_content_type("application/json") ··· 63 92 {:ok, did} 64 93 end 65 94 end 95 + 96 + defp parse_limit(%{"limit" => limit_str}) do 97 + case Integer.parse(limit_str) do 98 + {limit, ""} when limit >= 1 and limit <= 100 -> {:ok, limit} 99 + _ -> {:error, "limit must be an integer between 1 and 100"} 100 + end 101 + end 102 + 103 + defp parse_limit(_), do: {:ok, 50} 104 + 105 + defp parse_cursor(%{"cursor" => cursor_str}) do 106 + case Integer.parse(cursor_str) do 107 + {offset, ""} when offset >= 0 -> {:ok, offset} 108 + _ -> {:error, "cursor must be a valid non-negative integer"} 109 + end 110 + end 111 + 112 + defp parse_cursor(_), do: {:ok, 0} 66 113 67 114 defp to_vouch_view(vouch) do 68 115 %Defs.VouchView{
+84
appview/test/atvouch/xrpc_vouches_test.exs
··· 181 181 assert conn.status == 401 182 182 end 183 183 end 184 + 185 + describe "GET /xrpc/dev.atvouch.graph.getEntireGraph" do 186 + test "returns all vouches with default limit" do 187 + conn = 188 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph") 189 + |> Atvouch.Router.call(@opts) 190 + 191 + assert conn.status == 200 192 + body = Jason.decode!(conn.resp_body) 193 + assert length(body["vouches"]) == 3 194 + refute body["cursor"] 195 + end 196 + 197 + test "paginates with limit" do 198 + conn = 199 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph?limit=2") 200 + |> Atvouch.Router.call(@opts) 201 + 202 + assert conn.status == 200 203 + body = Jason.decode!(conn.resp_body) 204 + assert length(body["vouches"]) == 2 205 + assert body["cursor"] 206 + end 207 + 208 + test "cursor returns next page" do 209 + # First page 210 + conn1 = 211 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph?limit=2") 212 + |> Atvouch.Router.call(@opts) 213 + 214 + body1 = Jason.decode!(conn1.resp_body) 215 + assert length(body1["vouches"]) == 2 216 + cursor = body1["cursor"] 217 + assert cursor 218 + 219 + # Second page 220 + conn2 = 221 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph?limit=2&cursor=#{cursor}") 222 + |> Atvouch.Router.call(@opts) 223 + 224 + body2 = Jason.decode!(conn2.resp_body) 225 + assert length(body2["vouches"]) == 1 226 + refute body2["cursor"] 227 + 228 + # No overlap between pages 229 + uris1 = Enum.map(body1["vouches"], & &1["uri"]) 230 + uris2 = Enum.map(body2["vouches"], & &1["uri"]) 231 + assert MapSet.disjoint?(MapSet.new(uris1), MapSet.new(uris2)) 232 + end 233 + 234 + test "works without authentication" do 235 + conn = 236 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph") 237 + |> Atvouch.Router.call(@opts) 238 + 239 + assert conn.status == 200 240 + body = Jason.decode!(conn.resp_body) 241 + assert length(body["vouches"]) == 3 242 + end 243 + 244 + test "returns 400 with invalid cursor" do 245 + conn = 246 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph?cursor=notanumber") 247 + |> Atvouch.Router.call(@opts) 248 + 249 + assert conn.status == 400 250 + end 251 + 252 + test "returns 400 with limit over 100" do 253 + conn = 254 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph?limit=101") 255 + |> Atvouch.Router.call(@opts) 256 + 257 + assert conn.status == 400 258 + end 259 + 260 + test "returns 400 with limit under 1" do 261 + conn = 262 + conn(:get, "/xrpc/dev.atvouch.graph.getEntireGraph?limit=0") 263 + |> Atvouch.Router.call(@opts) 264 + 265 + assert conn.status == 400 266 + end 267 + end 184 268 end
+39
lexicons/dev/atvouch/graph/getEntireGraph.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "dev.atvouch.graph.getEntireGraph", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Returns every vouch in the graph with cursor-based pagination.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "minimum": 1, 14 + "maximum": 100, 15 + "default": 50 16 + }, 17 + "cursor": { "type": "string" } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["vouches"], 25 + "properties": { 26 + "cursor": { "type": "string" }, 27 + "vouches": { 28 + "type": "array", 29 + "items": { 30 + "type": "ref", 31 + "ref": "dev.atvouch.graph.defs#vouchView" 32 + } 33 + } 34 + } 35 + } 36 + } 37 + } 38 + } 39 + }