From 98ed136c3e1eb03ba54a4faf1edb3f133461ece7 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 18:10:39 -0700 Subject: [PATCH 001/138] GraphQL API WIP --- lib/ret/hub.ex | 16 ++++++++++++ lib/ret_web/endpoint.ex | 1 + lib/ret_web/resolvers/room_resolver.ex | 28 +++++++++++++++++++++ lib/ret_web/router.ex | 7 ++++++ lib/ret_web/schema.ex | 14 +++++++++++ lib/ret_web/schema/room_types.ex | 35 ++++++++++++++++++++++++++ mix.exs | 4 +++ mix.lock | 5 ++++ 8 files changed, 110 insertions(+) create mode 100644 lib/ret_web/resolvers/room_resolver.ex create mode 100644 lib/ret_web/schema.ex create mode 100644 lib/ret_web/schema/room_types.ex diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index f5c16b4ad..f16bf830a 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -133,6 +133,14 @@ defmodule Ret.Hub do |> changeset(scene_or_scene_listing, params) end + def create(params) do + scene_or_scene_listing = get_scene_or_scene_listing(params) + + %Hub{} + |> changeset(scene_or_scene_listing, params) + |> Repo.insert() + end + defp get_scene_or_scene_listing(params) do if is_nil(params["scene_id"]) do SceneListing.get_random_default_scene_listing() @@ -148,6 +156,14 @@ defmodule Ret.Hub do end end + def get_public_rooms(page, page_size) do + Hub + |> where([h], h.allow_promotion and h.entry_mode == ^"allow") + |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) + |> order_by(desc: :inserted_at) + |> Repo.paginate(%{page: page, page_size: page_size}) + end + def changeset(%Hub{} = hub, %Scene{} = scene, attrs) do hub |> changeset(nil, attrs) diff --git a/lib/ret_web/endpoint.ex b/lib/ret_web/endpoint.ex index ec120ca14..dff9480b0 100644 --- a/lib/ret_web/endpoint.ex +++ b/lib/ret_web/endpoint.ex @@ -1,6 +1,7 @@ defmodule RetWeb.Endpoint do use Phoenix.Endpoint, otp_app: :ret use Sentry.Phoenix.Endpoint + use Absinthe.Phoenix.Endpoint socket("/socket", RetWeb.SessionSocket, websocket: [check_origin: {RetWeb.Endpoint, :allowed_origin?, []}]) diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex new file mode 100644 index 000000000..a333e56f6 --- /dev/null +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -0,0 +1,28 @@ +defmodule RetWeb.Resolvers.RoomResolver do + alias Ret.Hub + + def list_rooms(_parent, _args, _resolutions) do + {:ok, IO.inspect(Hub.get_public_rooms(0, 10))} + end + + def create_room(_parent, args, _resolutions) do + args + |> Hub.create() + |> case do + {:ok, room} -> + {:ok, room} + + {:error, changeset} -> + {:error, extract_error_message(changeset)} + end + end + + defp extract_error_message(changeset) do + Enum.map(changeset.errors, fn {field, {error, _details}} -> + [ + field: field, + message: String.capitalize(error) + ] + end) + end +end \ No newline at end of file diff --git a/lib/ret_web/router.ex b/lib/ret_web/router.ex index 56e94c89d..3e302869a 100644 --- a/lib/ret_web/router.ex +++ b/lib/ret_web/router.ex @@ -142,6 +142,13 @@ defmodule RetWeb.Router do end end + scope "/api/v2", as: :api_v2 do + pipe_through([:parsed_body, :api] ++ if(Mix.env() == :prod, do: [:ssl_only], else: [])) + + forward "/graphiql", Absinthe.Plug.GraphiQL, schema: RetWeb.Schema + forward "/", Absinthe.Plug, schema: RetWeb.Schema + end + # Directly accessible APIs. # Permit direct file uploads without intermediate ALB/Cloudfront/CDN proxying. scope "/api", RetWeb do diff --git a/lib/ret_web/schema.ex b/lib/ret_web/schema.ex new file mode 100644 index 000000000..13e0a46c4 --- /dev/null +++ b/lib/ret_web/schema.ex @@ -0,0 +1,14 @@ +defmodule RetWeb.Schema do + use Absinthe.Schema + + import_types(Absinthe.Type.Custom) + import_types(RetWeb.Schema.RoomTypes) + + query do + import_fields(:room_queries) + end + + mutation do + import_fields(:room_mutations) + end +end \ No newline at end of file diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex new file mode 100644 index 000000000..a3faefbd5 --- /dev/null +++ b/lib/ret_web/schema/room_types.ex @@ -0,0 +1,35 @@ +defmodule RetWeb.Schema.RoomTypes do + use Absinthe.Schema.Notation + alias RetWeb.Resolvers + + object :room do + field :hub_sid, :id, name: "id" + field :name, :string + end + + object :room_list do + field(:total_entries, :integer) + field(:total_pages, :integer) + field(:page_number, :integer) + field(:page_size, :integer) + field(:entries, list_of(:room)) + end + + object :room_queries do + field :list_rooms, :room_list do + resolve(&Resolvers.RoomResolver.list_rooms/3) + end + end + + object :room_mutations do + field :create_room, :room do + arg(:name, non_null(:string)) + + resolve(&Resolvers.RoomResolver.create_room/3) + end + end + + object :room_subscriptions do + field :room_created, :room + end +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index fc41a4973..ac7cff47a 100644 --- a/mix.exs +++ b/mix.exs @@ -41,6 +41,10 @@ defmodule Ret.Mixfile do # Avoid 3.4.0 for now bc https://github.com/elixir-ecto/ecto/issues/3246 {:ecto, "~> 3.3.0"}, {:ecto_sql, "~> 3.3.0"}, + {:absinthe, "~> 1.4"}, + {:dataloader, "~> 1.0.0"}, + {:absinthe_plug, "~> 1.4"}, + {:absinthe_phoenix, "~> 1.4.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.13"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, diff --git a/mix.lock b/mix.lock index a18979f76..06b363ac9 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,8 @@ %{ + "absinthe": {:hex, :absinthe, "1.4.16", "0933e4d9f12652b12115d5709c0293a1bf78a22578032e9ad0dad4efee6b9eb1", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "076b8bd9552f4966ba1242f412f6c439b731169a36a0ddaaffcd3893828f5bf6"}, + "absinthe_ecto": {:hex, :absinthe_ecto, "0.1.3", "420b68129e79fe4571a4838904ba03e282330d335da47729ad52ffd7b8c5fcb1", [:mix], [{:absinthe, "~> 1.3.0 or ~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "355b9db34abfab96ae1e025434b66e11002babcf4fe6b7144d26ff7548985f52"}, + "absinthe_phoenix": {:hex, :absinthe_phoenix, "1.4.4", "af3b7b44483121f756ea0ec75a536b74f67cdd62ec6a34b9e58df1fb4662389e", [:mix], [{:absinthe, "~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.4.0", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "54118c32ca00257b3cd3e616b3f9cee99e493d2399528334cbb5457e470400d3"}, + "absinthe_plug": {:hex, :absinthe_plug, "1.4.7", "939b6b9e1c7abc6b399a5b49faa690a1fbb55b195c670aa35783b14b08ccec7a", [:mix], [{:absinthe, "~> 1.4.11", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c6ecb0e56a963287ac252d0563e5b33b84b300ce8203d3d1410dddb5dc6d08e9"}, "artificery": {:hex, :artificery, "0.4.2", "3ded6e29e13113af52811c72f414d1e88f711410cac1b619ab3a2666bbd7efd4", [:mix], [], "hexpm", "514586f4312ef3709a3ccbd8e55f69455add235c1729656687bb781d10d0afdb"}, "bamboo": {:hex, :bamboo, "1.4.0", "7b9201c49a843e4802061cf45692405b2c00efcf1cebf8b7b64f015ead072392", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b9cad03bf38c7f37b6308876039355665b6ce09fefb46dc529cef4def912cffa"}, "bamboo_smtp": {:hex, :bamboo_smtp, "1.7.0", "f0d213e18ced1f08b551a72221e9b8cfbf23d592b684e9aa1ef5250f4943ef9b", [:mix], [{:bamboo, "~> 1.2", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.14.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "6093e44cf93ae70c1e06637a80f71cb24b7a848777b20a52f7036431d7e02249"}, @@ -14,6 +18,7 @@ "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm", "1e1a3d176d52daebbecbbcdfd27c27726076567905c2a9d7398c54da9d225761"}, "credo": {:hex, :credo, "1.3.2", "08d456dcf3c24da162d02953fb07267e444469d8dad3a2ae47794938ea467b3a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b11d28cce1f1f399dddffd42d8e21dcad783309e230f84b70267b1a5546468b6"}, "crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1347d889d1a0eda997990876b4894359e34bfbbd688acbb0ba28a2795ca40685"}, + "dataloader": {:hex, :dataloader, "1.0.7", "58351b335673cf40601429bfed6c11fece6ce7ad169b2ac0f0fe83e716587391", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "12bf66478e4a5085d09dc96932d058c206ee8c219cc7691d12a40dc35c8cefaa"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "deferred_config": {:hex, :deferred_config, "0.1.1", "ec912e9ee3c99b90a8d4bdec8fbd15309f4bd6729f30789e0ff6f595d06bbce5", [:mix], [], "hexpm", "2eb5311037feb4a6a5dbe3ecc5c98af7ea849730e5dbd9aee0f45c5dbccc3922"}, From 55b1260d7f47a7522fdc8ac47e82a93fa8852b6c Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 18:19:37 -0700 Subject: [PATCH 002/138] Fix formatting and remove inspect --- lib/ret/hub.ex | 8 ++++---- lib/ret_web/resolvers/room_resolver.ex | 4 ++-- lib/ret_web/schema.ex | 2 +- lib/ret_web/schema/room_types.ex | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index f16bf830a..d3c328290 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -158,10 +158,10 @@ defmodule Ret.Hub do def get_public_rooms(page, page_size) do Hub - |> where([h], h.allow_promotion and h.entry_mode == ^"allow") - |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) - |> order_by(desc: :inserted_at) - |> Repo.paginate(%{page: page, page_size: page_size}) + |> where([h], h.allow_promotion and h.entry_mode == ^"allow") + |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) + |> order_by(desc: :inserted_at) + |> Repo.paginate(%{page: page, page_size: page_size}) end def changeset(%Hub{} = hub, %Scene{} = scene, attrs) do diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index a333e56f6..17d48c5e0 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -2,7 +2,7 @@ defmodule RetWeb.Resolvers.RoomResolver do alias Ret.Hub def list_rooms(_parent, _args, _resolutions) do - {:ok, IO.inspect(Hub.get_public_rooms(0, 10))} + {:ok, Hub.get_public_rooms(0, 10)} end def create_room(_parent, args, _resolutions) do @@ -25,4 +25,4 @@ defmodule RetWeb.Resolvers.RoomResolver do ] end) end -end \ No newline at end of file +end diff --git a/lib/ret_web/schema.ex b/lib/ret_web/schema.ex index 13e0a46c4..a3003a720 100644 --- a/lib/ret_web/schema.ex +++ b/lib/ret_web/schema.ex @@ -11,4 +11,4 @@ defmodule RetWeb.Schema do mutation do import_fields(:room_mutations) end -end \ No newline at end of file +end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index a3faefbd5..1e1b40e4c 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -3,8 +3,8 @@ defmodule RetWeb.Schema.RoomTypes do alias RetWeb.Resolvers object :room do - field :hub_sid, :id, name: "id" - field :name, :string + field(:hub_sid, :id, name: "id") + field(:name, :string) end object :room_list do @@ -30,6 +30,6 @@ defmodule RetWeb.Schema.RoomTypes do end object :room_subscriptions do - field :room_created, :room + field(:room_created, :room) end -end \ No newline at end of file +end From bd512d59d21ff91f2325838d3e1eedf2cbb6bc75 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 18:47:00 -0700 Subject: [PATCH 003/138] Use a changeset error handling middleware --- .../middlewares/handle_changeset_errors.ex | 15 +++++++++++++++ lib/ret_web/resolvers/room_resolver.ex | 19 +------------------ lib/ret_web/schema.ex | 5 +++++ 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 lib/ret_web/middlewares/handle_changeset_errors.ex diff --git a/lib/ret_web/middlewares/handle_changeset_errors.ex b/lib/ret_web/middlewares/handle_changeset_errors.ex new file mode 100644 index 000000000..a01f15778 --- /dev/null +++ b/lib/ret_web/middlewares/handle_changeset_errors.ex @@ -0,0 +1,15 @@ +defmodule RetWeb.Middlewares.HandleChangesetErrors do + @behaviour Absinthe.Middleware + def call(resolution, _) do + %{resolution | + errors: Enum.flat_map(resolution.errors, &handle_error/1) + } + end + + defp handle_error(%Ecto.Changeset{} = changeset) do + changeset + |> Ecto.Changeset.traverse_errors(fn {err, _opts} -> err end) + |> Enum.map(fn({k,v}) -> "#{k}: #{v}" end) + end + defp handle_error(error), do: [error] +end \ No newline at end of file diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 17d48c5e0..dd655154c 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -6,23 +6,6 @@ defmodule RetWeb.Resolvers.RoomResolver do end def create_room(_parent, args, _resolutions) do - args - |> Hub.create() - |> case do - {:ok, room} -> - {:ok, room} - - {:error, changeset} -> - {:error, extract_error_message(changeset)} - end - end - - defp extract_error_message(changeset) do - Enum.map(changeset.errors, fn {field, {error, _details}} -> - [ - field: field, - message: String.capitalize(error) - ] - end) + Hub.create(args) end end diff --git a/lib/ret_web/schema.ex b/lib/ret_web/schema.ex index a3003a720..5acb2287b 100644 --- a/lib/ret_web/schema.ex +++ b/lib/ret_web/schema.ex @@ -1,6 +1,11 @@ defmodule RetWeb.Schema do use Absinthe.Schema + def middleware(middleware, _field, %{identifier: :mutation}) do + middleware ++ [RetWeb.Middlewares.HandleChangesetErrors] + end + def middleware(middleware, _field, _object), do: middleware + import_types(Absinthe.Type.Custom) import_types(RetWeb.Schema.RoomTypes) From 3da617c737cc7fa4d008c90ca800698f52b085ca Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 19:18:44 -0700 Subject: [PATCH 004/138] Add pagination to public rooms query --- lib/ret/hub.ex | 4 ++-- lib/ret/repo.ex | 2 +- lib/ret_web/resolvers/room_resolver.ex | 4 ++-- lib/ret_web/schema/room_types.ex | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index d3c328290..25649853d 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -156,12 +156,12 @@ defmodule Ret.Hub do end end - def get_public_rooms(page, page_size) do + def get_public_rooms(params) do Hub |> where([h], h.allow_promotion and h.entry_mode == ^"allow") |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) |> order_by(desc: :inserted_at) - |> Repo.paginate(%{page: page, page_size: page_size}) + |> Repo.paginate(params) end def changeset(%Hub{} = hub, %Scene{} = scene, attrs) do diff --git a/lib/ret/repo.ex b/lib/ret/repo.ex index 188946024..987d9cb40 100644 --- a/lib/ret/repo.ex +++ b/lib/ret/repo.ex @@ -1,6 +1,6 @@ defmodule Ret.Repo do use Ecto.Repo, otp_app: :ret, adapter: Ecto.Adapters.Postgres - use Scrivener, page_size: 20 + use Scrivener, page_size: 20, max_page_size: 50 def init(_, opts) do {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))} diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index dd655154c..710d6dacd 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,8 +1,8 @@ defmodule RetWeb.Resolvers.RoomResolver do alias Ret.Hub - def list_rooms(_parent, _args, _resolutions) do - {:ok, Hub.get_public_rooms(0, 10)} + def list_rooms(_parent, args, _resolutions) do + {:ok, Hub.get_public_rooms(args)} end def create_room(_parent, args, _resolutions) do diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 1e1b40e4c..a5739f4eb 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -17,6 +17,8 @@ defmodule RetWeb.Schema.RoomTypes do object :room_queries do field :list_rooms, :room_list do + arg(:page, :integer) + arg(:page_size, :integer) resolve(&Resolvers.RoomResolver.list_rooms/3) end end From d396147cc1b30e8e8c9ecda259784327bef522ba Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 21:21:01 -0700 Subject: [PATCH 005/138] Add myRooms and authenticated routes --- lib/ret/hub.ex | 8 ++++++++ lib/ret_web/context.ex | 25 +++++++++++++++++++++++++ lib/ret_web/resolvers/room_resolver.ex | 10 +++++++++- lib/ret_web/router.ex | 7 +++++-- lib/ret_web/schema/room_types.ex | 10 ++++++++-- 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 lib/ret_web/context.ex diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 25649853d..464231b46 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -156,6 +156,14 @@ defmodule Ret.Hub do end end + def get_my_rooms(account, params) do + Hub + |> where([h], h.created_by_account_id == ^account.account_id) + |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) + |> order_by(desc: :inserted_at) + |> Repo.paginate(params) + end + def get_public_rooms(params) do Hub |> where([h], h.allow_promotion and h.entry_mode == ^"allow") diff --git a/lib/ret_web/context.ex b/lib/ret_web/context.ex new file mode 100644 index 000000000..06b0db43f --- /dev/null +++ b/lib/ret_web/context.ex @@ -0,0 +1,25 @@ +defmodule RetWeb.Context do + @behaviour Plug + + import Plug.Conn + import Ecto.Query, only: [where: 2] + + alias RetWeb.{Repo, User} + + def init(opts), do: opts + + def call(conn, _) do + context = build_context(conn) + Absinthe.Plug.put_options(conn, context: context) + end + + def build_context(conn) do + account = Guardian.Plug.current_resource(conn) + + if account do + %{account: account} + else + %{} + end + end +end \ No newline at end of file diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 710d6dacd..1d3ea5f81 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,7 +1,15 @@ defmodule RetWeb.Resolvers.RoomResolver do alias Ret.Hub - def list_rooms(_parent, args, _resolutions) do + def my_rooms(_parent, args, %{context: %{account: account} }) do + {:ok, Hub.get_my_rooms(account, args)} + end + + def my_rooms(_parent, args, _resolutions) do + {:error, "Not authorized"} + end + + def public_rooms(_parent, args, _resolutions) do {:ok, Hub.get_public_rooms(args)} end diff --git a/lib/ret_web/router.ex b/lib/ret_web/router.ex index 3e302869a..2074465b6 100644 --- a/lib/ret_web/router.ex +++ b/lib/ret_web/router.ex @@ -65,6 +65,10 @@ defmodule RetWeb.Router do plug(RetWeb.Plugs.RedirectToMainDomain) end + pipeline :graphql do + plug RetWeb.Context + end + scope "/health", RetWeb do get("/", HealthController, :index) end @@ -143,8 +147,7 @@ defmodule RetWeb.Router do end scope "/api/v2", as: :api_v2 do - pipe_through([:parsed_body, :api] ++ if(Mix.env() == :prod, do: [:ssl_only], else: [])) - + pipe_through([:parsed_body, :api, :auth_optional, :graphql] ++ if(Mix.env() == :prod, do: [:ssl_only], else: [])) forward "/graphiql", Absinthe.Plug.GraphiQL, schema: RetWeb.Schema forward "/", Absinthe.Plug, schema: RetWeb.Schema end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index a5739f4eb..7865d169a 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -16,10 +16,16 @@ defmodule RetWeb.Schema.RoomTypes do end object :room_queries do - field :list_rooms, :room_list do + field :my_rooms, :room_list do arg(:page, :integer) arg(:page_size, :integer) - resolve(&Resolvers.RoomResolver.list_rooms/3) + resolve(&Resolvers.RoomResolver.my_rooms/3) + end + + field :public_rooms, :room_list do + arg(:page, :integer) + arg(:page_size, :integer) + resolve(&Resolvers.RoomResolver.public_rooms/3) end end From 8ea148a70deb9007a65a7cdebb213086e1799274 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 21:51:41 -0700 Subject: [PATCH 006/138] Add favorite rooms query --- lib/ret/hub.ex | 14 ++++++++++++-- lib/ret_web/resolvers/room_resolver.ex | 8 ++++++++ lib/ret_web/schema/room_types.ex | 6 ++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 464231b46..5e06bfb21 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -24,7 +24,8 @@ defmodule Ret.Hub do RoomAssigner, BitFieldUtils, HubRoleMembership, - AppConfig + AppConfig, + AccountFavorite } alias Ret.Hub.{HubSlug} @@ -158,12 +159,21 @@ defmodule Ret.Hub do def get_my_rooms(account, params) do Hub - |> where([h], h.created_by_account_id == ^account.account_id) + |> where([h], h.created_by_account_id == ^account.account_id and h.entry_mode == ^"allow") |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) |> order_by(desc: :inserted_at) |> Repo.paginate(params) end + def get_favorite_rooms(account, params) do + Hub + |> where([h], h.entry_mode == ^"allow") + |> join(:inner, [h], f in AccountFavorite, on: f.hub_id == h.hub_id and f.account_id == ^account.account_id) + |> order_by([h, f], desc: f.last_activated_at) + |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) + |> Repo.paginate(params) + end + def get_public_rooms(params) do Hub |> where([h], h.allow_promotion and h.entry_mode == ^"allow") diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 1d3ea5f81..6e8f5f98a 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -9,6 +9,14 @@ defmodule RetWeb.Resolvers.RoomResolver do {:error, "Not authorized"} end + def favorite_rooms(_parent, args, %{context: %{account: account} }) do + {:ok, Hub.get_favorite_rooms(account, args)} + end + + def favorite_rooms(_parent, args, _resolutions) do + {:error, "Not authorized"} + end + def public_rooms(_parent, args, _resolutions) do {:ok, Hub.get_public_rooms(args)} end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 7865d169a..408d04ab6 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -27,6 +27,12 @@ defmodule RetWeb.Schema.RoomTypes do arg(:page_size, :integer) resolve(&Resolvers.RoomResolver.public_rooms/3) end + + field :favorite_rooms, :room_list do + arg(:page, :integer) + arg(:page_size, :integer) + resolve(&Resolvers.RoomResolver.favorite_rooms/3) + end end object :room_mutations do From c28f8fb9c9e0ada2e718407602407e45f6fcda3f Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 15 Jul 2020 23:57:10 -0700 Subject: [PATCH 007/138] Add more room and scene fields --- lib/ret/hub.ex | 14 +++++++ lib/ret_web/resolvers/room_resolver.ex | 41 +++++++++++++++++++++ lib/ret_web/schema.ex | 1 + lib/ret_web/schema/room_types.ex | 51 ++++++++++++++++++++++++++ lib/ret_web/schema/scene_types.ex | 23 ++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 lib/ret_web/schema/scene_types.ex diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 5e06bfb21..62ffcfd3c 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -383,6 +383,15 @@ defmodule Ret.Hub do hub.room_size || AppConfig.get_cached_config_value("features|default_room_size") end + def scene_or_scene_listing_for(%Hub{} = hub) do + case hub.scene || hub.scene_listing do + nil -> nil + %Scene{state: :removed} -> nil + %SceneListing{state: :delisted} -> nil + scene -> scene + end + end + defp changeset_for_new_entry_code(%Hub{} = hub) do hub |> Ecto.Changeset.change() @@ -576,6 +585,11 @@ defmodule Ret.Hub do |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) end + def member_permissions_for_hub_as_atoms(%Hub{} = hub) do + hub.member_permissions + |> BitFieldUtils.permissions_to_map(@member_permissions) + end + # The account argument here can be a Ret.Account, a Ret.OAuthProvider or nil. def perms_for_account(%Ret.Hub{} = hub, account) do %{ diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 6e8f5f98a..5b19963e5 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,5 +1,6 @@ defmodule RetWeb.Resolvers.RoomResolver do alias Ret.Hub + import Canada, only: [can?: 2] def my_rooms(_parent, args, %{context: %{account: account} }) do {:ok, Hub.get_my_rooms(account, args)} @@ -24,4 +25,44 @@ defmodule RetWeb.Resolvers.RoomResolver do def create_room(_parent, args, _resolutions) do Hub.create(args) end + + def embed_token(hub, _args, %{context: %{account: account} }) do + if account |> can?(embed_hub(hub)) do + {:ok, hub.embed_token} + else + {:ok, nil} + end + end + + def embed_token(_hub, _args, _resolutions) do + {:ok, nil} + end + + def port(_hub, _args, _resolutions) do + {:ok, Hub.janus_port()} + end + + def turn(_hub, _args, _resolutions) do + {:ok, Hub.generate_turn_info()} + end + + def member_permissions(hub, _args, _resolutions) do + {:ok, Hub.member_permissions_for_hub_as_atoms(hub)} + end + + def room_size(hub, _args, _resolutions) do + {:ok, Hub.room_size_for(hub)} + end + + def member_count(hub, _args, _resolutions) do + {:ok, Hub.member_count_for(hub)} + end + + def lobby_count(hub, _args, _resolutions) do + {:ok, Hub.lobby_count_for(hub)} + end + + def scene(hub, _args, _resolutions) do + {:ok, Hub.scene_or_scene_listing_for(hub)} + end end diff --git a/lib/ret_web/schema.ex b/lib/ret_web/schema.ex index 5acb2287b..22608b276 100644 --- a/lib/ret_web/schema.ex +++ b/lib/ret_web/schema.ex @@ -8,6 +8,7 @@ defmodule RetWeb.Schema do import_types(Absinthe.Type.Custom) import_types(RetWeb.Schema.RoomTypes) + import_types(RetWeb.Schema.SceneTypes) query do import_fields(:room_queries) diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 408d04ab6..26d43ba82 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -2,9 +2,60 @@ defmodule RetWeb.Schema.RoomTypes do use Absinthe.Schema.Notation alias RetWeb.Resolvers + object :turn_transport do + field(:port, :integer) + end + + object :turn_info do + field(:credential, :string) + field(:enabled, :boolean) + field(:transports, list_of(:turn_transport)) + field(:username, :string) + end + + object :member_permissions do + field(:spawn_and_move_media, :boolean) + field(:spawn_camera, :boolean) + field(:spawn_drawing, :boolean) + field(:pin_objects, :boolean) + field(:spawn_emoji, :boolean) + field(:fly, :boolean) + end + object :room do field(:hub_sid, :id, name: "id") field(:name, :string) + field(:slug, :string) + field(:description, :string) + field(:allow_promotion, :boolean) + field(:entry_code, :string) + field(:entry_mode, :string) + field(:host, :string) + field(:port, :integer) do + resolve(&Resolvers.RoomResolver.port/3) + end + field(:turn, :turn_info) do + resolve(&Resolvers.RoomResolver.turn/3) + end + field(:embed_token, :string) do + resolve(&Resolvers.RoomResolver.embed_token/3) + end + field(:member_permissions, :member_permissions) do + resolve(&Resolvers.RoomResolver.member_permissions/3) + end + field(:room_size, :integer) do + resolve(&Resolvers.RoomResolver.room_size/3) + end + field(:member_count, :integer) do + resolve(&Resolvers.RoomResolver.member_count/3) + end + field(:lobby_count, :integer) do + resolve(&Resolvers.RoomResolver.lobby_count/3) + end + field(:scene, :scene_or_scene_listing) do + resolve(&Resolvers.RoomResolver.scene/3) + end + # TODO: Figure out user_data end object :room_list do diff --git a/lib/ret_web/schema/scene_types.ex b/lib/ret_web/schema/scene_types.ex new file mode 100644 index 000000000..2c19153a7 --- /dev/null +++ b/lib/ret_web/schema/scene_types.ex @@ -0,0 +1,23 @@ +defmodule RetWeb.Schema.SceneTypes do + use Absinthe.Schema.Notation + alias RetWeb.Resolvers + alias Ret.{Scene, SceneListing} + + union :scene_or_scene_listing do + types [:scene, :scene_listing] + resolve_type fn + %Scene{}, _ -> :scene + %SceneListing{}, _ -> :scene_listing + end + end + + object :scene do + field(:scene_sid, :id, name: "id") + field(:name, :string) + end + + object :scene_listing do + field(:scene_listing_sid, :id, name: "id") + field(:name, :string) + end +end From 6e718e1551cfa3e76ddc2d41b77c449c1cc0b36f Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 16 Jul 2020 00:52:42 -0700 Subject: [PATCH 008/138] Use dataloader for batch fetching scenes --- lib/ret/hub.ex | 3 --- lib/ret/scene.ex | 7 +++++++ lib/ret_web/schema.ex | 14 ++++++++++++++ lib/ret_web/schema/room_types.ex | 4 +++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 62ffcfd3c..b97020b9b 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -160,7 +160,6 @@ defmodule Ret.Hub do def get_my_rooms(account, params) do Hub |> where([h], h.created_by_account_id == ^account.account_id and h.entry_mode == ^"allow") - |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) |> order_by(desc: :inserted_at) |> Repo.paginate(params) end @@ -170,14 +169,12 @@ defmodule Ret.Hub do |> where([h], h.entry_mode == ^"allow") |> join(:inner, [h], f in AccountFavorite, on: f.hub_id == h.hub_id and f.account_id == ^account.account_id) |> order_by([h, f], desc: f.last_activated_at) - |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) |> Repo.paginate(params) end def get_public_rooms(params) do Hub |> where([h], h.allow_promotion and h.entry_mode == ^"allow") - |> preload(scene: [:screenshot_owned_file], scene_listing: [:scene, :screenshot_owned_file]) |> order_by(desc: :inserted_at) |> Repo.paginate(params) end diff --git a/lib/ret/scene.ex b/lib/ret/scene.ex index cbc4cbe5e..cc30c3fa8 100644 --- a/lib/ret/scene.ex +++ b/lib/ret/scene.ex @@ -9,6 +9,7 @@ end defmodule Ret.Scene do use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Ret.{Repo, Scene, SceneListing, Project, Storage} alias Ret.Scene.{SceneSlug} @@ -54,6 +55,12 @@ defmodule Ret.Scene do timestamps() end + # Dataloader + def data(), do: Dataloader.Ecto.new(Repo, query: &query/2) + # Guard against loading removed or delested scenes/scene listings + def query(Scene, _), do: from s in Scene, where: s.state != ^:removed + def query(SceneListing, _), do: from sl in SceneListing, where: sl.state != ^:delisted + def scene_or_scene_listing_by_sid(sid) do Scene |> Repo.get_by(scene_sid: sid) || SceneListing |> Repo.get_by(scene_listing_sid: sid) |> Repo.preload(scene: Scene.scene_preloads()) diff --git a/lib/ret_web/schema.ex b/lib/ret_web/schema.ex index 22608b276..47fc2f83a 100644 --- a/lib/ret_web/schema.ex +++ b/lib/ret_web/schema.ex @@ -1,5 +1,6 @@ defmodule RetWeb.Schema do use Absinthe.Schema + alias Ret.Scene def middleware(middleware, _field, %{identifier: :mutation}) do middleware ++ [RetWeb.Middlewares.HandleChangesetErrors] @@ -17,4 +18,17 @@ defmodule RetWeb.Schema do mutation do import_fields(:room_mutations) end + + def context(ctx) do + loader = + Dataloader.new + |> Dataloader.add_source(Scene, Scene.data()) + + Map.put(ctx, :loader, loader) + end + + def plugins do + [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() + end + end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 26d43ba82..0a2b3a2a1 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -1,6 +1,8 @@ defmodule RetWeb.Schema.RoomTypes do use Absinthe.Schema.Notation alias RetWeb.Resolvers + alias Ret.Scene + import Absinthe.Resolution.Helpers, only: [dataloader: 1] object :turn_transport do field(:port, :integer) @@ -53,7 +55,7 @@ defmodule RetWeb.Schema.RoomTypes do resolve(&Resolvers.RoomResolver.lobby_count/3) end field(:scene, :scene_or_scene_listing) do - resolve(&Resolvers.RoomResolver.scene/3) + resolve(dataloader(Scene)) end # TODO: Figure out user_data end From b269d9ba81c791567f03b3f2f405562d592d50ba Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 11:26:09 -0700 Subject: [PATCH 009/138] Fix typo in comment --- lib/ret/scene.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ret/scene.ex b/lib/ret/scene.ex index cc30c3fa8..e4a1a761a 100644 --- a/lib/ret/scene.ex +++ b/lib/ret/scene.ex @@ -57,7 +57,7 @@ defmodule Ret.Scene do # Dataloader def data(), do: Dataloader.Ecto.new(Repo, query: &query/2) - # Guard against loading removed or delested scenes/scene listings + # Guard against loading removed scenes or delisted scene listings def query(Scene, _), do: from s in Scene, where: s.state != ^:removed def query(SceneListing, _), do: from sl in SceneListing, where: sl.state != ^:delisted From ada62762aac46c74137bbe9e64c8e1cebe35fb1e Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 11:26:28 -0700 Subject: [PATCH 010/138] specify preferred json codec --- lib/ret_web/router.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ret_web/router.ex b/lib/ret_web/router.ex index 2074465b6..539b1c8a9 100644 --- a/lib/ret_web/router.ex +++ b/lib/ret_web/router.ex @@ -148,7 +148,7 @@ defmodule RetWeb.Router do scope "/api/v2", as: :api_v2 do pipe_through([:parsed_body, :api, :auth_optional, :graphql] ++ if(Mix.env() == :prod, do: [:ssl_only], else: [])) - forward "/graphiql", Absinthe.Plug.GraphiQL, schema: RetWeb.Schema + forward "/graphiql", Absinthe.Plug.GraphiQL, json_codec: Jason, schema: RetWeb.Schema forward "/", Absinthe.Plug, schema: RetWeb.Schema end From bdc1f8d846f7f56331b123c0a9fd0d3a0c6b3c80 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:09:04 -0700 Subject: [PATCH 011/138] Add test cases for room query --- test/api/v2/room_query_test.exs | 86 +++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/api/v2/room_query_test.exs diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs new file mode 100644 index 000000000..24c14e2d9 --- /dev/null +++ b/test/api/v2/room_query_test.exs @@ -0,0 +1,86 @@ +defmodule RoomQueryTest do + @moduledoc """ + Test absinthe queries on rooms + """ + + use ExUnit.Case + use RetWeb.ConnCase + import Ret.TestHelpers + + setup_all do + Absinthe.Test.prime(RetWeb.Schema) + end + + test "anyone can query for public rooms", %{conn: conn} do + account = create_random_account() + scene = create_scene(account) + {:ok, hub: public_hub} = create_public_hub(%{scene: scene}) + + query = """ + query { + publicRooms { + entries { + id + } + } + } + """ + + res = + conn + |> post("/api/v2/graphiql", %{ + "query" => query, + "variables" => "{}" + }) + |> json_response(200) + + rooms = res["data"]["publicRooms"]["entries"] + assert List.first(rooms)["id"] == public_hub.hub_sid + end + + test "private room queries require authentication", %{conn: conn} do + account = create_random_account() + scene = create_scene(account) + {:ok, hub: hub} = create_hub(%{scene: scene}) + hub + |> Ret.Repo.preload(created_by_account: []) + |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) + |> Ret.Repo.update!() + + query = """ + query { + myRooms { + entries { + id + } + } + } + """ + + # Query without auth header + res = + conn + |> post("/api/v2/graphiql", %{ + "query" => query, + "variables" => "{}" + }) + |> json_response(200) + assert is_nil(res["data"]["myRooms"]) + error = List.first(res["errors"]) + assert error["message"] == "Not authorized" + assert List.first(error["path"]) == "myRooms" + + # Query with auth header + {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) + auth_res = + conn + |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> post("/api/v2/graphiql", %{ + "query" => query, + "variables" => "{}" + }) + |> json_response(200) + rooms = auth_res["data"]["myRooms"]["entries"] + assert List.first(rooms)["id"] == hub.hub_sid + end +end From 125769cc09fa48e4be788fd772fc03313acf7654 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:38:30 -0700 Subject: [PATCH 012/138] DRY up tests --- test/api/v2/room_query_test.exs | 162 +++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 25 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index 24c14e2d9..c4a28f1ca 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -11,12 +11,7 @@ defmodule RoomQueryTest do Absinthe.Test.prime(RetWeb.Schema) end - test "anyone can query for public rooms", %{conn: conn} do - account = create_random_account() - scene = create_scene(account) - {:ok, hub: public_hub} = create_public_hub(%{scene: scene}) - - query = """ + @query_public_rooms """ query { publicRooms { entries { @@ -24,12 +19,48 @@ defmodule RoomQueryTest do } } } - """ + """ + + @query_my_rooms """ + query { + myRooms { + entries { + id + } + } + } + """ + + @query_favorite_rooms """ + query { + favoriteRooms { + entries { + id + } + } + } + """ + + setup _context do + account = create_random_account() + account2 = create_random_account() + scene = create_scene(account) + {:ok, hub: hub} = create_hub(%{scene: scene}) + {:ok, hub: public_hub} = create_public_hub(%{scene: scene}) + + %{ + account: account, + account2: account2, + hub: hub, + public_hub: public_hub + } + end + test "anyone can query for public rooms", %{conn: conn, public_hub: public_hub} do res = conn |> post("/api/v2/graphiql", %{ - "query" => query, + "query" => @query_public_rooms, "variables" => "{}" }) |> json_response(200) @@ -38,49 +69,130 @@ defmodule RoomQueryTest do assert List.first(rooms)["id"] == public_hub.hub_sid end - test "private room queries require authentication", %{conn: conn} do - account = create_random_account() - scene = create_scene(account) - {:ok, hub: hub} = create_hub(%{scene: scene}) + test "cannot query my rooms without authentication", %{conn: conn, account: account, hub: hub} do hub |> Ret.Repo.preload(created_by_account: []) |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) |> Ret.Repo.update!() - query = """ - query { - myRooms { - entries { - id - } - } - } - """ - # Query without auth header res = conn |> post("/api/v2/graphiql", %{ - "query" => query, + "query" => @query_my_rooms, "variables" => "{}" }) |> json_response(200) + assert is_nil(res["data"]["myRooms"]) error = List.first(res["errors"]) assert error["message"] == "Not authorized" assert List.first(error["path"]) == "myRooms" + end + + test "anyone can query for their own rooms when authenticated", %{ + conn: conn, + account: account, + hub: hub + } do + hub + |> Ret.Repo.preload(created_by_account: []) + |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) + |> Ret.Repo.update!() - # Query with auth header {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) + + auth_res = + conn + |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> post("/api/v2/graphiql", %{ + "query" => @query_my_rooms, + "variables" => "{}" + }) + |> json_response(200) + + rooms = auth_res["data"]["myRooms"]["entries"] + assert List.first(rooms)["id"] == hub.hub_sid + end + + test "my rooms only returns my own rooms", %{conn: conn, account: account, account2: account2, hub: hub} do + hub + |> Ret.Repo.preload(created_by_account: []) + |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) + |> Ret.Repo.update!() + + {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account2) + auth_res = conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) |> post("/api/v2/graphiql", %{ - "query" => query, + "query" => @query_my_rooms, "variables" => "{}" }) |> json_response(200) + rooms = auth_res["data"]["myRooms"]["entries"] + assert Enum.empty?(rooms) + end + + test "cannot query favorite rooms without authentication", %{conn: conn, account: account, hub: hub} do + Ret.AccountFavorite.ensure_favorited(hub, account) + + # Query without auth header + res = + conn + |> post("/api/v2/graphiql", %{ + "query" => @query_favorite_rooms, + "variables" => "{}" + }) + |> json_response(200) + + assert is_nil(res["data"]["favoriteRooms"]) + error = List.first(res["errors"]) + assert error["message"] == "Not authorized" + assert List.first(error["path"]) == "favoriteRooms" + end + + test "anyone can query favorite rooms when authenticated", %{conn: conn, account: account, hub: hub} do + Ret.AccountFavorite.ensure_favorited(hub, account) + + # Query with auth header + {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) + + res = + conn + |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> post("/api/v2/graphiql", %{ + "query" => @query_favorite_rooms, + "variables" => "{}" + }) + |> json_response(200) + + rooms = res["data"]["favoriteRooms"]["entries"] assert List.first(rooms)["id"] == hub.hub_sid end + + test "favorite rooms only returns your own favorites", %{ + conn: conn, + account: account, + account2: account2, + hub: hub + } do + Ret.AccountFavorite.ensure_favorited(hub, account) + + {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account2) + + res = + conn + |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> post("/api/v2/graphiql", %{ + "query" => @query_favorite_rooms, + "variables" => "{}" + }) + |> json_response(200) + + rooms = res["data"]["favoriteRooms"]["entries"] + assert Enum.empty?(rooms) + end end From 6883a09413e682f80bbee8d601230e863500dd9d Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:39:32 -0700 Subject: [PATCH 013/138] Remove unused "variables" from tests --- test/api/v2/room_query_test.exs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index c4a28f1ca..154117113 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -60,8 +60,7 @@ defmodule RoomQueryTest do res = conn |> post("/api/v2/graphiql", %{ - "query" => @query_public_rooms, - "variables" => "{}" + "query" => @query_public_rooms }) |> json_response(200) @@ -79,8 +78,7 @@ defmodule RoomQueryTest do res = conn |> post("/api/v2/graphiql", %{ - "query" => @query_my_rooms, - "variables" => "{}" + "query" => @query_my_rooms }) |> json_response(200) @@ -106,8 +104,7 @@ defmodule RoomQueryTest do conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) |> post("/api/v2/graphiql", %{ - "query" => @query_my_rooms, - "variables" => "{}" + "query" => @query_my_rooms }) |> json_response(200) @@ -127,8 +124,7 @@ defmodule RoomQueryTest do conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) |> post("/api/v2/graphiql", %{ - "query" => @query_my_rooms, - "variables" => "{}" + "query" => @query_my_rooms }) |> json_response(200) @@ -143,8 +139,7 @@ defmodule RoomQueryTest do res = conn |> post("/api/v2/graphiql", %{ - "query" => @query_favorite_rooms, - "variables" => "{}" + "query" => @query_favorite_rooms }) |> json_response(200) @@ -164,8 +159,7 @@ defmodule RoomQueryTest do conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) |> post("/api/v2/graphiql", %{ - "query" => @query_favorite_rooms, - "variables" => "{}" + "query" => @query_favorite_rooms }) |> json_response(200) @@ -187,8 +181,7 @@ defmodule RoomQueryTest do conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) |> post("/api/v2/graphiql", %{ - "query" => @query_favorite_rooms, - "variables" => "{}" + "query" => @query_favorite_rooms }) |> json_response(200) From 1a5853f69397abf8403f9682413d2c836cb0ff0c Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:43:43 -0700 Subject: [PATCH 014/138] DRY : add assign_creator function --- test/api/v2/room_query_test.exs | 15 +++------------ test/support/test_helpers.ex | 8 ++++++++ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index 154117113..e20edb24c 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -69,10 +69,7 @@ defmodule RoomQueryTest do end test "cannot query my rooms without authentication", %{conn: conn, account: account, hub: hub} do - hub - |> Ret.Repo.preload(created_by_account: []) - |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) - |> Ret.Repo.update!() + assign_creator(hub, account) # Query without auth header res = @@ -93,10 +90,7 @@ defmodule RoomQueryTest do account: account, hub: hub } do - hub - |> Ret.Repo.preload(created_by_account: []) - |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) - |> Ret.Repo.update!() + assign_creator(hub, account) {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) @@ -113,10 +107,7 @@ defmodule RoomQueryTest do end test "my rooms only returns my own rooms", %{conn: conn, account: account, account2: account2, hub: hub} do - hub - |> Ret.Repo.preload(created_by_account: []) - |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) - |> Ret.Repo.update!() + assign_creator(hub, account) {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account2) diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index b57418f80..583c947ef 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -212,4 +212,12 @@ defmodule Ret.TestHelpers do conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) end + + def assign_creator(hub, account) do + hub + |> Ret.Repo.preload(created_by_account: []) + |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) + |> Ret.Repo.update!() + end + end From b585a8c751a2074a6c6a43065708bc4424022e69 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:46:06 -0700 Subject: [PATCH 015/138] Rename put_auth_header_for_email --- test/ret_web/controllers/api/app_config_controller_test.exs | 4 ++-- test/ret_web/controllers/api/hub_controller_test.exs | 2 +- test/support/conn_case.ex | 2 +- test/support/test_helpers.ex | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/ret_web/controllers/api/app_config_controller_test.exs b/test/ret_web/controllers/api/app_config_controller_test.exs index eed31a809..3f515fc56 100644 --- a/test/ret_web/controllers/api/app_config_controller_test.exs +++ b/test/ret_web/controllers/api/app_config_controller_test.exs @@ -8,7 +8,7 @@ defmodule RetWeb.AppConfigControllerTest do test "admins can create app configs", %{conn: conn} do conn - |> Ret.TestHelpers.put_auth_header_for_account("admin@mozilla.com") + |> Ret.TestHelpers.put_auth_header_for_email("admin@mozilla.com") |> create_app_config(%{"test-config" => "foo"}) |> response(200) @@ -18,7 +18,7 @@ defmodule RetWeb.AppConfigControllerTest do test "non-admins cannot create app configs", %{conn: conn} do %{status: 401} = conn - |> Ret.TestHelpers.put_auth_header_for_account("test2@mozilla.com") + |> Ret.TestHelpers.put_auth_header_for_email("test2@mozilla.com") |> create_app_config(%{"test-config" => "bar"}) end diff --git a/test/ret_web/controllers/api/hub_controller_test.exs b/test/ret_web/controllers/api/hub_controller_test.exs index 6f848214a..59c6dc5e7 100644 --- a/test/ret_web/controllers/api/hub_controller_test.exs +++ b/test/ret_web/controllers/api/hub_controller_test.exs @@ -28,7 +28,7 @@ defmodule RetWeb.HubControllerTest do disabled_account |> Ecto.Changeset.change(state: :disabled) |> Ret.Repo.update!() conn - |> put_auth_header_for_account("disabled_account@mozilla.com") + |> put_auth_header_for_email("disabled_account@mozilla.com") |> create_hub("Test Hub") |> response(401) end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 7fdd8f9ee..a7538b1bd 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -37,7 +37,7 @@ defmodule RetWeb.ConnCase do conn = if tags[:authenticated] do - conn |> Ret.TestHelpers.put_auth_header_for_account("test@mozilla.com") + conn |> Ret.TestHelpers.put_auth_header_for_email("test@mozilla.com") else conn end diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index 583c947ef..c956ee054 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -204,7 +204,7 @@ defmodule Ret.TestHelpers do File.rm_rf(Application.get_env(:ret, Storage)[:storage_path]) end - def put_auth_header_for_account(conn, email) do + def put_auth_header_for_email(conn, email) do {:ok, token, _claims} = email |> Ret.Account.find_or_create_account_for_email() From 002f1e948b01cf5d6ac44d5cd34779badbc0fff6 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:51:38 -0700 Subject: [PATCH 016/138] DRY: put_auth_header_for_account --- test/api/v2/room_query_test.exs | 17 ++++------------- test/support/test_helpers.ex | 10 +++++----- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index e20edb24c..68a09344f 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -92,11 +92,9 @@ defmodule RoomQueryTest do } do assign_creator(hub, account) - {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) - auth_res = conn - |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> put_auth_header_for_account(account) |> post("/api/v2/graphiql", %{ "query" => @query_my_rooms }) @@ -109,11 +107,9 @@ defmodule RoomQueryTest do test "my rooms only returns my own rooms", %{conn: conn, account: account, account2: account2, hub: hub} do assign_creator(hub, account) - {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account2) - auth_res = conn - |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> put_auth_header_for_account(account2) |> post("/api/v2/graphiql", %{ "query" => @query_my_rooms }) @@ -143,12 +139,9 @@ defmodule RoomQueryTest do test "anyone can query favorite rooms when authenticated", %{conn: conn, account: account, hub: hub} do Ret.AccountFavorite.ensure_favorited(hub, account) - # Query with auth header - {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) - res = conn - |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> put_auth_header_for_account(account) |> post("/api/v2/graphiql", %{ "query" => @query_favorite_rooms }) @@ -166,11 +159,9 @@ defmodule RoomQueryTest do } do Ret.AccountFavorite.ensure_favorited(hub, account) - {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account2) - res = conn - |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) + |> put_auth_header_for_account(account2) |> post("/api/v2/graphiql", %{ "query" => @query_favorite_rooms }) diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index c956ee054..3955bba41 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -205,10 +205,11 @@ defmodule Ret.TestHelpers do end def put_auth_header_for_email(conn, email) do - {:ok, token, _claims} = - email - |> Ret.Account.find_or_create_account_for_email() - |> Ret.Guardian.encode_and_sign() + put_auth_header_for_account(conn, Ret.Account.find_or_create_account_for_email(email)) + end + + def put_auth_header_for_account(conn, account) do + {:ok, token, _claims} = Ret.Guardian.encode_and_sign(account) conn |> Plug.Conn.put_req_header("authorization", "bearer: " <> token) end @@ -219,5 +220,4 @@ defmodule Ret.TestHelpers do |> Ret.Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) |> Ret.Repo.update!() end - end From 66d8c4af5fc8fbc5abc9e7fcf8e53ad566df5d7e Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:53:40 -0700 Subject: [PATCH 017/138] DRY: graphql query --- test/api/v2/room_query_test.exs | 45 +++++++++++---------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index 68a09344f..a82f98d8d 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -56,13 +56,18 @@ defmodule RoomQueryTest do } end + defp query(conn, query) do + conn + |> post("/api/v2/graphiql", %{ + "query" => "#{query}" + }) + |> json_response(200) + end + test "anyone can query for public rooms", %{conn: conn, public_hub: public_hub} do res = conn - |> post("/api/v2/graphiql", %{ - "query" => @query_public_rooms - }) - |> json_response(200) + |> query(@query_public_rooms) rooms = res["data"]["publicRooms"]["entries"] assert List.first(rooms)["id"] == public_hub.hub_sid @@ -71,13 +76,9 @@ defmodule RoomQueryTest do test "cannot query my rooms without authentication", %{conn: conn, account: account, hub: hub} do assign_creator(hub, account) - # Query without auth header res = conn - |> post("/api/v2/graphiql", %{ - "query" => @query_my_rooms - }) - |> json_response(200) + |> query(@query_my_rooms) assert is_nil(res["data"]["myRooms"]) error = List.first(res["errors"]) @@ -95,10 +96,7 @@ defmodule RoomQueryTest do auth_res = conn |> put_auth_header_for_account(account) - |> post("/api/v2/graphiql", %{ - "query" => @query_my_rooms - }) - |> json_response(200) + |> query(@query_my_rooms) rooms = auth_res["data"]["myRooms"]["entries"] assert List.first(rooms)["id"] == hub.hub_sid @@ -110,10 +108,7 @@ defmodule RoomQueryTest do auth_res = conn |> put_auth_header_for_account(account2) - |> post("/api/v2/graphiql", %{ - "query" => @query_my_rooms - }) - |> json_response(200) + |> query(@query_my_rooms) rooms = auth_res["data"]["myRooms"]["entries"] assert Enum.empty?(rooms) @@ -122,13 +117,9 @@ defmodule RoomQueryTest do test "cannot query favorite rooms without authentication", %{conn: conn, account: account, hub: hub} do Ret.AccountFavorite.ensure_favorited(hub, account) - # Query without auth header res = conn - |> post("/api/v2/graphiql", %{ - "query" => @query_favorite_rooms - }) - |> json_response(200) + |> query(@query_favorite_rooms) assert is_nil(res["data"]["favoriteRooms"]) error = List.first(res["errors"]) @@ -142,10 +133,7 @@ defmodule RoomQueryTest do res = conn |> put_auth_header_for_account(account) - |> post("/api/v2/graphiql", %{ - "query" => @query_favorite_rooms - }) - |> json_response(200) + |> query(@query_favorite_rooms) rooms = res["data"]["favoriteRooms"]["entries"] assert List.first(rooms)["id"] == hub.hub_sid @@ -162,10 +150,7 @@ defmodule RoomQueryTest do res = conn |> put_auth_header_for_account(account2) - |> post("/api/v2/graphiql", %{ - "query" => @query_favorite_rooms - }) - |> json_response(200) + |> query(@query_favorite_rooms) rooms = res["data"]["favoriteRooms"]["entries"] assert Enum.empty?(rooms) From b7c55e57dd407b390add133ef24fed015fc8e39d Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 14:58:12 -0700 Subject: [PATCH 018/138] Don't need to hit the iql api --- test/api/v2/room_query_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index a82f98d8d..e9563725d 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -58,7 +58,7 @@ defmodule RoomQueryTest do defp query(conn, query) do conn - |> post("/api/v2/graphiql", %{ + |> post("/api/v2/", %{ "query" => "#{query}" }) |> json_response(200) From 96d27c8ef76f7e7297678ea3c4992d663b40ec4d Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 15:31:55 -0700 Subject: [PATCH 019/138] Test room creation. Add default creator assignment --- lib/ret_web/resolvers/room_resolver.ex | 21 +++++++-- test/api/v2/room_query_test.exs | 65 ++++++++++++++++++++------ 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 5b19963e5..b590b9019 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -2,7 +2,7 @@ defmodule RetWeb.Resolvers.RoomResolver do alias Ret.Hub import Canada, only: [can?: 2] - def my_rooms(_parent, args, %{context: %{account: account} }) do + def my_rooms(_parent, args, %{context: %{account: account}}) do {:ok, Hub.get_my_rooms(account, args)} end @@ -10,7 +10,7 @@ defmodule RetWeb.Resolvers.RoomResolver do {:error, "Not authorized"} end - def favorite_rooms(_parent, args, %{context: %{account: account} }) do + def favorite_rooms(_parent, args, %{context: %{account: account}}) do {:ok, Hub.get_favorite_rooms(account, args)} end @@ -22,15 +22,26 @@ defmodule RetWeb.Resolvers.RoomResolver do {:ok, Hub.get_public_rooms(args)} end + def create_room(_parent, args, %{context: %{account: account}}) do + {:ok, hub} = Hub.create(args) + + hub + |> Ret.Repo.preload(Hub.hub_preloads()) + |> Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) + |> Ret.Repo.update!() + + {:ok, hub} + end + def create_room(_parent, args, _resolutions) do Hub.create(args) end - def embed_token(hub, _args, %{context: %{account: account} }) do + def embed_token(hub, _args, %{context: %{account: account}}) do if account |> can?(embed_hub(hub)) do {:ok, hub.embed_token} else - {:ok, nil} + {:ok, nil} end end @@ -44,7 +55,7 @@ defmodule RetWeb.Resolvers.RoomResolver do def turn(_hub, _args, _resolutions) do {:ok, Hub.generate_turn_info()} - end + end def member_permissions(hub, _args, _resolutions) do {:ok, Hub.member_permissions_for_hub_as_atoms(hub)} diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index e9563725d..be174fd95 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -41,6 +41,23 @@ defmodule RoomQueryTest do } """ + @mutation_create_room """ + mutation MyCooLmutatuon($roomName: String!){ + createRoom (name: $roomName) { + id + } + } + """ + + defp do_graphql_action(conn, query, variables \\ %{}) do + conn + |> post("/api/v2/", %{ + "query" => "#{query}", + "variables" => variables + }) + |> json_response(200) + end + setup _context do account = create_random_account() account2 = create_random_account() @@ -56,18 +73,10 @@ defmodule RoomQueryTest do } end - defp query(conn, query) do - conn - |> post("/api/v2/", %{ - "query" => "#{query}" - }) - |> json_response(200) - end - test "anyone can query for public rooms", %{conn: conn, public_hub: public_hub} do res = conn - |> query(@query_public_rooms) + |> do_graphql_action(@query_public_rooms) rooms = res["data"]["publicRooms"]["entries"] assert List.first(rooms)["id"] == public_hub.hub_sid @@ -78,7 +87,7 @@ defmodule RoomQueryTest do res = conn - |> query(@query_my_rooms) + |> do_graphql_action(@query_my_rooms) assert is_nil(res["data"]["myRooms"]) error = List.first(res["errors"]) @@ -96,7 +105,7 @@ defmodule RoomQueryTest do auth_res = conn |> put_auth_header_for_account(account) - |> query(@query_my_rooms) + |> do_graphql_action(@query_my_rooms) rooms = auth_res["data"]["myRooms"]["entries"] assert List.first(rooms)["id"] == hub.hub_sid @@ -108,7 +117,7 @@ defmodule RoomQueryTest do auth_res = conn |> put_auth_header_for_account(account2) - |> query(@query_my_rooms) + |> do_graphql_action(@query_my_rooms) rooms = auth_res["data"]["myRooms"]["entries"] assert Enum.empty?(rooms) @@ -119,7 +128,7 @@ defmodule RoomQueryTest do res = conn - |> query(@query_favorite_rooms) + |> do_graphql_action(@query_favorite_rooms) assert is_nil(res["data"]["favoriteRooms"]) error = List.first(res["errors"]) @@ -133,7 +142,7 @@ defmodule RoomQueryTest do res = conn |> put_auth_header_for_account(account) - |> query(@query_favorite_rooms) + |> do_graphql_action(@query_favorite_rooms) rooms = res["data"]["favoriteRooms"]["entries"] assert List.first(rooms)["id"] == hub.hub_sid @@ -150,9 +159,35 @@ defmodule RoomQueryTest do res = conn |> put_auth_header_for_account(account2) - |> query(@query_favorite_rooms) + |> do_graphql_action(@query_favorite_rooms) rooms = res["data"]["favoriteRooms"]["entries"] assert Enum.empty?(rooms) end + + test "anyone can create a room", %{ + conn: conn + } do + res = + conn + |> do_graphql_action(@mutation_create_room, %{roomName: "my fun room"}) + + id = res["data"]["createRoom"]["id"] + assert !is_nil(id) + hub = Ret.Repo.get_by(Ret.Hub, hub_sid: id) + assert hub.name =~ "my fun room" + end + + test "Creating a room while authenticated assigns the creator", %{ + conn: conn, + account: account + } do + res = + conn + |> put_auth_header_for_account(account) + |> do_graphql_action(@mutation_create_room, %{roomName: "my fun room"}) + + hub = Ret.Repo.get_by(Ret.Hub, hub_sid: res["data"]["createRoom"]["id"]) + assert hub.created_by_account_id == account.account_id + end end From 6b4c0b6666067120fb52d19548f91a271ed0c832 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 15:35:53 -0700 Subject: [PATCH 020/138] Specify json_codec --- lib/ret_web/router.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ret_web/router.ex b/lib/ret_web/router.ex index 539b1c8a9..4816f9a57 100644 --- a/lib/ret_web/router.ex +++ b/lib/ret_web/router.ex @@ -149,7 +149,7 @@ defmodule RetWeb.Router do scope "/api/v2", as: :api_v2 do pipe_through([:parsed_body, :api, :auth_optional, :graphql] ++ if(Mix.env() == :prod, do: [:ssl_only], else: [])) forward "/graphiql", Absinthe.Plug.GraphiQL, json_codec: Jason, schema: RetWeb.Schema - forward "/", Absinthe.Plug, schema: RetWeb.Schema + forward "/", Absinthe.Plug, json_codec: Jason, schema: RetWeb.Schema end # Directly accessible APIs. From fec37538a579a77ceba96886da122577d92748e4 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 15:42:06 -0700 Subject: [PATCH 021/138] Add room name to mutation result --- test/api/v2/room_query_test.exs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index be174fd95..175f5c0c9 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -44,7 +44,8 @@ defmodule RoomQueryTest do @mutation_create_room """ mutation MyCooLmutatuon($roomName: String!){ createRoom (name: $roomName) { - id + id, + name } } """ @@ -168,14 +169,16 @@ defmodule RoomQueryTest do test "anyone can create a room", %{ conn: conn } do + roomName = "my fun room" res = conn - |> do_graphql_action(@mutation_create_room, %{roomName: "my fun room"}) + |> do_graphql_action(@mutation_create_room, %{roomName: roomName}) id = res["data"]["createRoom"]["id"] assert !is_nil(id) hub = Ret.Repo.get_by(Ret.Hub, hub_sid: id) - assert hub.name =~ "my fun room" + assert hub.name == res["data"]["createRoom"]["name"] + assert hub.name == roomName end test "Creating a room while authenticated assigns the creator", %{ From 012d0405ef87557d43484ce1052607e12483601b Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 16:44:26 -0700 Subject: [PATCH 022/138] Test pagination --- test/api/v2/room_query_test.exs | 54 +++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/test/api/v2/room_query_test.exs b/test/api/v2/room_query_test.exs index 175f5c0c9..62d85a486 100644 --- a/test/api/v2/room_query_test.exs +++ b/test/api/v2/room_query_test.exs @@ -22,11 +22,15 @@ defmodule RoomQueryTest do """ @query_my_rooms """ - query { - myRooms { + query ($page: Int, $page_size: Int){ + myRooms (page: $page, page_size: $page_size){ entries { id } + total_entries + total_pages + page_number + page_size } } """ @@ -42,7 +46,7 @@ defmodule RoomQueryTest do """ @mutation_create_room """ - mutation MyCooLmutatuon($roomName: String!){ + mutation ($roomName: String!){ createRoom (name: $roomName) { id, name @@ -69,6 +73,7 @@ defmodule RoomQueryTest do %{ account: account, account2: account2, + scene: scene, hub: hub, public_hub: public_hub } @@ -169,16 +174,17 @@ defmodule RoomQueryTest do test "anyone can create a room", %{ conn: conn } do - roomName = "my fun room" + room_name = "my fun room" + res = conn - |> do_graphql_action(@mutation_create_room, %{roomName: roomName}) + |> do_graphql_action(@mutation_create_room, %{roomName: room_name}) id = res["data"]["createRoom"]["id"] assert !is_nil(id) hub = Ret.Repo.get_by(Ret.Hub, hub_sid: id) assert hub.name == res["data"]["createRoom"]["name"] - assert hub.name == roomName + assert hub.name == room_name end test "Creating a room while authenticated assigns the creator", %{ @@ -193,4 +199,40 @@ defmodule RoomQueryTest do hub = Ret.Repo.get_by(Ret.Hub, hub_sid: res["data"]["createRoom"]["id"]) assert hub.created_by_account_id == account.account_id end + + test "The room query api paginates results", %{ + conn: conn, + scene: scene, + account: account + } do + for _n <- 1..50 do + {:ok, hub: hub} = create_hub(%{scene: scene}) + assign_creator(hub, account) + end + + response = + conn + |> put_auth_header_for_account(account) + |> do_graphql_action(@query_my_rooms) + + assert length(response["data"]["myRooms"]["entries"]) === response["data"]["myRooms"]["page_size"] + end + + test "The room query api paginates results 2", %{ + conn: conn, + scene: scene, + account: account + } do + for _n <- 1..50 do + {:ok, hub: hub} = create_hub(%{scene: scene}) + assign_creator(hub, account) + end + + response = + conn + |> put_auth_header_for_account(account) + |> do_graphql_action(@query_my_rooms, %{page: 3, page_size: 24}) + + assert length(response["data"]["myRooms"]["entries"]) === 2 + end end From 0e3f85e7c723085d9ff15f29f33e134bed620fc5 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 16:48:16 -0700 Subject: [PATCH 023/138] Fix warnings --- lib/ret_web/context.ex | 7 +------ lib/ret_web/resolvers/room_resolver.ex | 4 ++-- lib/ret_web/schema/scene_types.ex | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/ret_web/context.ex b/lib/ret_web/context.ex index 06b0db43f..1ee555e80 100644 --- a/lib/ret_web/context.ex +++ b/lib/ret_web/context.ex @@ -1,11 +1,6 @@ defmodule RetWeb.Context do @behaviour Plug - import Plug.Conn - import Ecto.Query, only: [where: 2] - - alias RetWeb.{Repo, User} - def init(opts), do: opts def call(conn, _) do @@ -22,4 +17,4 @@ defmodule RetWeb.Context do %{} end end -end \ No newline at end of file +end diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index b590b9019..3fe0102ee 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -6,7 +6,7 @@ defmodule RetWeb.Resolvers.RoomResolver do {:ok, Hub.get_my_rooms(account, args)} end - def my_rooms(_parent, args, _resolutions) do + def my_rooms(_parent, _args, _resolutions) do {:error, "Not authorized"} end @@ -14,7 +14,7 @@ defmodule RetWeb.Resolvers.RoomResolver do {:ok, Hub.get_favorite_rooms(account, args)} end - def favorite_rooms(_parent, args, _resolutions) do + def favorite_rooms(_parent, _args, _resolutions) do {:error, "Not authorized"} end diff --git a/lib/ret_web/schema/scene_types.ex b/lib/ret_web/schema/scene_types.ex index 2c19153a7..a17905c5d 100644 --- a/lib/ret_web/schema/scene_types.ex +++ b/lib/ret_web/schema/scene_types.ex @@ -1,6 +1,5 @@ defmodule RetWeb.Schema.SceneTypes do use Absinthe.Schema.Notation - alias RetWeb.Resolvers alias Ret.{Scene, SceneListing} union :scene_or_scene_listing do From 8f2611c5a82f4b618e643e24754415490d3fcbb0 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Thu, 30 Jul 2020 16:56:03 -0700 Subject: [PATCH 024/138] Formatting --- lib/ret/scene.ex | 4 ++-- lib/ret_web/middlewares/handle_changeset_errors.ex | 11 +++++------ lib/ret_web/schema.ex | 8 ++++---- lib/ret_web/schema/room_types.ex | 9 +++++++++ lib/ret_web/schema/scene_types.ex | 7 ++++--- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/ret/scene.ex b/lib/ret/scene.ex index e4a1a761a..e509b27f6 100644 --- a/lib/ret/scene.ex +++ b/lib/ret/scene.ex @@ -58,8 +58,8 @@ defmodule Ret.Scene do # Dataloader def data(), do: Dataloader.Ecto.new(Repo, query: &query/2) # Guard against loading removed scenes or delisted scene listings - def query(Scene, _), do: from s in Scene, where: s.state != ^:removed - def query(SceneListing, _), do: from sl in SceneListing, where: sl.state != ^:delisted + def query(Scene, _), do: from(s in Scene, where: s.state != ^:removed) + def query(SceneListing, _), do: from(sl in SceneListing, where: sl.state != ^:delisted) def scene_or_scene_listing_by_sid(sid) do Scene |> Repo.get_by(scene_sid: sid) || diff --git a/lib/ret_web/middlewares/handle_changeset_errors.ex b/lib/ret_web/middlewares/handle_changeset_errors.ex index a01f15778..cf2c81ae7 100644 --- a/lib/ret_web/middlewares/handle_changeset_errors.ex +++ b/lib/ret_web/middlewares/handle_changeset_errors.ex @@ -1,15 +1,14 @@ defmodule RetWeb.Middlewares.HandleChangesetErrors do @behaviour Absinthe.Middleware def call(resolution, _) do - %{resolution | - errors: Enum.flat_map(resolution.errors, &handle_error/1) - } + %{resolution | errors: Enum.flat_map(resolution.errors, &handle_error/1)} end defp handle_error(%Ecto.Changeset{} = changeset) do changeset - |> Ecto.Changeset.traverse_errors(fn {err, _opts} -> err end) - |> Enum.map(fn({k,v}) -> "#{k}: #{v}" end) + |> Ecto.Changeset.traverse_errors(fn {err, _opts} -> err end) + |> Enum.map(fn {k, v} -> "#{k}: #{v}" end) end + defp handle_error(error), do: [error] -end \ No newline at end of file +end diff --git a/lib/ret_web/schema.ex b/lib/ret_web/schema.ex index 47fc2f83a..8df97402d 100644 --- a/lib/ret_web/schema.ex +++ b/lib/ret_web/schema.ex @@ -5,6 +5,7 @@ defmodule RetWeb.Schema do def middleware(middleware, _field, %{identifier: :mutation}) do middleware ++ [RetWeb.Middlewares.HandleChangesetErrors] end + def middleware(middleware, _field, _object), do: middleware import_types(Absinthe.Type.Custom) @@ -21,14 +22,13 @@ defmodule RetWeb.Schema do def context(ctx) do loader = - Dataloader.new + Dataloader.new() |> Dataloader.add_source(Scene, Scene.data()) - + Map.put(ctx, :loader, loader) end - + def plugins do [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() end - end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 0a2b3a2a1..2092d465f 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -33,30 +33,39 @@ defmodule RetWeb.Schema.RoomTypes do field(:entry_code, :string) field(:entry_mode, :string) field(:host, :string) + field(:port, :integer) do resolve(&Resolvers.RoomResolver.port/3) end + field(:turn, :turn_info) do resolve(&Resolvers.RoomResolver.turn/3) end + field(:embed_token, :string) do resolve(&Resolvers.RoomResolver.embed_token/3) end + field(:member_permissions, :member_permissions) do resolve(&Resolvers.RoomResolver.member_permissions/3) end + field(:room_size, :integer) do resolve(&Resolvers.RoomResolver.room_size/3) end + field(:member_count, :integer) do resolve(&Resolvers.RoomResolver.member_count/3) end + field(:lobby_count, :integer) do resolve(&Resolvers.RoomResolver.lobby_count/3) end + field(:scene, :scene_or_scene_listing) do resolve(dataloader(Scene)) end + # TODO: Figure out user_data end diff --git a/lib/ret_web/schema/scene_types.ex b/lib/ret_web/schema/scene_types.ex index a17905c5d..5db059926 100644 --- a/lib/ret_web/schema/scene_types.ex +++ b/lib/ret_web/schema/scene_types.ex @@ -3,11 +3,12 @@ defmodule RetWeb.Schema.SceneTypes do alias Ret.{Scene, SceneListing} union :scene_or_scene_listing do - types [:scene, :scene_listing] - resolve_type fn + types([:scene, :scene_listing]) + + resolve_type(fn %Scene{}, _ -> :scene %SceneListing{}, _ -> :scene_listing - end + end) end object :scene do From e60ddff7b6a4a6c5214b2f23c3936c4f0ef14abf Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 17 Aug 2020 14:22:44 -0700 Subject: [PATCH 025/138] Add mutation for updating room name --- lib/ret_web/api_ecto_error_helpers.ex | 80 ++++++++++++++++++++++++++ lib/ret_web/resolvers/room_resolver.ex | 51 +++++++++++++++- lib/ret_web/schema/room_types.ex | 12 ++++ 3 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 lib/ret_web/api_ecto_error_helpers.ex diff --git a/lib/ret_web/api_ecto_error_helpers.ex b/lib/ret_web/api_ecto_error_helpers.ex new file mode 100644 index 000000000..a6be576cf --- /dev/null +++ b/lib/ret_web/api_ecto_error_helpers.ex @@ -0,0 +1,80 @@ +defmodule RetWeb.ApiEctoErrorHelpers do + # https://medium.com/@imanhodjaev/organising-absithe-and-ecto-errors-2a2257ccdff6 + + alias Ecto.Changeset + + @doc """ + Transform `%Ecto.Changeset{}` errors to a map + containing field name as a key on which validation + error happened and it's formatted message. + For example: + ``` + %{ + "title": "title length should be at least 8 characters" + ... + } + ``` + """ + def to_api_errors(%Changeset{} = changeset) do + changeset + |> format_errors() + |> concat_errors() + end + + # @doc """ + # Wrap context actions with Ecto. + # If action succeeds then the result tuple returned. + # If changeset errors happen then all errors transformed + # into convenient form of list of maps with fields on + # which validations failed. + # Else `unknown` error returned. + # """ + # def action_wrapped(fun) do + # case fun.() do + # {:ok, result} -> + # {:ok, result} + + # {:error, changeset = %Changeset{}} -> + # { + # :error, + # %{ + # message: "Changeset errors occurred", + # code: :schema_errors, + # errors: to_api_errors(changeset) + # } + # } + + # # Case for our standard errors in `IdpWeb.Schema.Errors` + # {:error, %{code: _}} = error -> + # error + + # {:error, _} -> + # { + # :error, + # %{ + # message: "Oops! Unknown error", + # code: :oops + # } + # } + # end + # end + + # Extract and interpolate all errors from + # `Changeset.errors` and return a map. + defp format_errors(%Changeset{} = changeset) do + Changeset.traverse_errors(changeset, fn {msg, opts} -> + Enum.reduce(opts, msg, fn {key, value}, formatted_msg -> + formatted_msg |> String.replace("%{#{key}}", to_string(value)) + end) + end) + end + + # Get all errors from `format_errors/1` and + # join their messages into a single string + # separated by "," + defp concat_errors(errors) do + Enum.reduce(errors, errors, fn {key, value}, map -> + map |> Map.put(key, Enum.join(value, ", ")) + end) + end +end diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 3fe0102ee..3669f0137 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,6 +1,7 @@ defmodule RetWeb.Resolvers.RoomResolver do - alias Ret.Hub + alias Ret.{Hub, Repo} import Canada, only: [can?: 2] + import RetWeb.ApiEctoErrorHelpers def my_rooms(_parent, args, %{context: %{account: account}}) do {:ok, Hub.get_my_rooms(account, args)} @@ -26,9 +27,9 @@ defmodule RetWeb.Resolvers.RoomResolver do {:ok, hub} = Hub.create(args) hub - |> Ret.Repo.preload(Hub.hub_preloads()) + |> Repo.preload(Hub.hub_preloads()) |> Hub.changeset_for_creator_assignment(account, hub.creator_assignment_token) - |> Ret.Repo.update!() + |> Repo.update!() {:ok, hub} end @@ -76,4 +77,48 @@ defmodule RetWeb.Resolvers.RoomResolver do def scene(hub, _args, _resolutions) do {:ok, Hub.scene_or_scene_listing_for(hub)} end + + def update_room(_, %{id: hub_sid} = args, %{context: %{account: account}}) do + hub = + Hub + |> Repo.get_by(hub_sid: hub_sid) + |> Repo.preload([:hub_role_memberships, :hub_bindings]) + + case hub do + nil -> + {:error, "Cannot find room with id " <> hub_sid} + + _ -> + case can?(account, update_roles(hub)) do + false -> + {:error, "Account does not have permission to update this hub."} + + true -> + changeset = + hub + |> Hub.add_attrs_to_changeset(args) + + # |> Hub.add_member_permissions_to_changeset(args) + # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) + + case changeset do + %{valid?: false} -> + {:error, + %{ + message: "Changeset errors occurred", + code: :schema_errors, + errors: to_api_errors(changeset) + }} + + _ -> + updated_hub = + changeset + |> Repo.update!() + |> Repo.preload(Hub.hub_preloads()) + + {:ok, updated_hub} + end + end + end + end end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 2092d465f..f026044e1 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -103,6 +103,18 @@ defmodule RetWeb.Schema.RoomTypes do resolve(&Resolvers.RoomResolver.create_room/3) end + field :update_room, :room do + arg(:id, :string) + arg(:name, :string) + # arg(:description, :string) + # arg(:room_size, :integer) + # arg(:scene_id, :string) + # TODO: promotion + # TODO: add/remove owner + # TODO: member_permissions + + resolve(&Resolvers.RoomResolver.update_room/3) + end end object :room_subscriptions do From 2f4c8bbc88a244af1ce5c01adfeb7d9b03065eba Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 09:08:52 -0700 Subject: [PATCH 026/138] Add capabilities to room resolver --- lib/ret/hub.ex | 10 +++++ lib/ret_web/resolvers/room_resolver.ex | 60 +++++++++++++++----------- lib/ret_web/schema/room_types.ex | 6 +-- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index b97020b9b..047f4e422 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -150,6 +150,13 @@ defmodule Ret.Hub do end end + def get_scene_or_scene_listing_by_id(nil) do + SceneListing.get_random_default_scene_listing() + end + def get_scene_or_scene_listing_by_id(id) do + Scene.scene_or_scene_listing_by_sid(id) + end + def get_by_entry_code_string(entry_code_string) when is_binary(entry_code_string) do case Integer.parse(entry_code_string) do {entry_code, _} -> Hub |> Repo.get_by(entry_code: entry_code) @@ -161,6 +168,7 @@ defmodule Ret.Hub do Hub |> where([h], h.created_by_account_id == ^account.account_id and h.entry_mode == ^"allow") |> order_by(desc: :inserted_at) + |> preload(^Hub.hub_preloads()) |> Repo.paginate(params) end @@ -169,6 +177,7 @@ defmodule Ret.Hub do |> where([h], h.entry_mode == ^"allow") |> join(:inner, [h], f in AccountFavorite, on: f.hub_id == h.hub_id and f.account_id == ^account.account_id) |> order_by([h, f], desc: f.last_activated_at) + |> preload(^Hub.hub_preloads()) |> Repo.paginate(params) end @@ -176,6 +185,7 @@ defmodule Ret.Hub do Hub |> where([h], h.allow_promotion and h.entry_mode == ^"allow") |> order_by(desc: :inserted_at) + |> preload(^Hub.hub_preloads()) |> Repo.paginate(params) end diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 3669f0137..1be9b6373 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,7 +1,8 @@ defmodule RetWeb.Resolvers.RoomResolver do - alias Ret.{Hub, Repo} + alias Ret.{Hub, Repo, Scene, SceneListing} import Canada, only: [can?: 2] import RetWeb.ApiEctoErrorHelpers + import Ecto.Changeset, only: [put_assoc: 3, change: 1] def my_rooms(_parent, args, %{context: %{account: account}}) do {:ok, Hub.get_my_rooms(account, args)} @@ -94,29 +95,40 @@ defmodule RetWeb.Resolvers.RoomResolver do {:error, "Account does not have permission to update this hub."} true -> - changeset = - hub - |> Hub.add_attrs_to_changeset(args) - - # |> Hub.add_member_permissions_to_changeset(args) - # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) - - case changeset do - %{valid?: false} -> - {:error, - %{ - message: "Changeset errors occurred", - code: :schema_errors, - errors: to_api_errors(changeset) - }} - - _ -> - updated_hub = - changeset - |> Repo.update!() - |> Repo.preload(Hub.hub_preloads()) - - {:ok, updated_hub} + scene_id = Map.get(args, :scene_id, nil) + scene_or_scene_listing = Hub.get_scene_or_scene_listing_by_id(scene_id) + + if scene_id && is_nil(scene_or_scene_listing) do + {:error, "Cannot find scene with id " <> scene_id} + else + changeset = + if scene_or_scene_listing, + do: Hub.changeset_for_new_scene(hub, scene_or_scene_listing), + else: change(hub) + + changeset = Hub.add_attrs_to_changeset(changeset, args) + + # TODO: Member permissions # |> Hub.add_member_permissions_to_changeset(args) + # TODO: Promotions # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) + + case changeset do + %{valid?: false} -> + {:error, + %{ + message: "Changeset errors occurred", + code: :schema_errors, + errors: to_api_errors(changeset) + }} + + _ -> + updated_hub = + changeset + |> Repo.update!() + |> Repo.preload(Hub.hub_preloads()) + + # TODO: Broadcast changes on hub_channel so room occupants know about changes + {:ok, updated_hub} + end end end end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index f026044e1..3f007d2a3 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -106,9 +106,9 @@ defmodule RetWeb.Schema.RoomTypes do field :update_room, :room do arg(:id, :string) arg(:name, :string) - # arg(:description, :string) - # arg(:room_size, :integer) - # arg(:scene_id, :string) + arg(:description, :string) + arg(:room_size, :integer) + arg(:scene_id, :string) # TODO: promotion # TODO: add/remove owner # TODO: member_permissions From ee0a66c32fdf6e30e0c2dc3d47def757d5759a6e Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 11:03:07 -0700 Subject: [PATCH 027/138] Refactor for readability --- lib/ret_web/auth_error_handler.ex | 9 +++ lib/ret_web/resolvers/room_resolver.ex | 101 +++++++++++++------------ lib/ret_web/schema/room_types.ex | 3 +- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/lib/ret_web/auth_error_handler.ex b/lib/ret_web/auth_error_handler.ex index 717125b16..6ba2b7351 100644 --- a/lib/ret_web/auth_error_handler.ex +++ b/lib/ret_web/auth_error_handler.ex @@ -1,8 +1,17 @@ defmodule RetWeb.Guardian.AuthErrorHandler do + @moduledoc false import Plug.Conn def auth_error(conn, {type, _reason}, _opts) do body = Poison.encode!(%{error: to_string(type)}) send_resp(conn, 401, body) + # TODO: GraphQL endpoint errors should be formatted with absinthe_plug + # "data" => %{ "action" => null } + # "errors" => [ + # %{ + # "message" => "Some error messages", + # "locations" => [%{"line" => 1, "column" => 2}] + # } + # ] end end diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 1be9b6373..cf8eda29b 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -80,57 +80,62 @@ defmodule RetWeb.Resolvers.RoomResolver do end def update_room(_, %{id: hub_sid} = args, %{context: %{account: account}}) do - hub = - Hub - |> Repo.get_by(hub_sid: hub_sid) - |> Repo.preload([:hub_role_memberships, :hub_bindings]) + hub = Hub |> Repo.get_by(hub_sid: hub_sid) |> Repo.preload([:hub_role_memberships, :hub_bindings]) + update_room_with_account(hub, account, args) + end + + defp update_room_with_account(nil, _account, %{id: hub_sid}) do + {:error, "Cannot find room with id " <> hub_sid} + end + + defp update_room_with_account(hub, account, args) do + case can?(account, update_roles(hub)) do + false -> + {:error, "Account does not have permission to update this hub."} + + true -> + # TODO: Member permissions # |> Hub.add_member_permissions_to_changeset(args) + # TODO: Promotions # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) + try_do_update_room(changeset_for_update_room(hub, args)) + end + end + + defp changeset_for_update_room(hub, %{scene_id: scene_id} = args) do + scene_or_scene_listing = Hub.get_scene_or_scene_listing_by_id(scene_id) - case hub do - nil -> - {:error, "Cannot find room with id " <> hub_sid} + if is_nil(scene_or_scene_listing) do + {:error, "Cannot find scene with id " <> scene_id} + else + Hub.changeset_for_new_scene(hub, scene_or_scene_listing) + |> Hub.add_attrs_to_changeset(args) + end + end + + defp changeset_for_update_room(hub, args) do + Hub.add_attrs_to_changeset(change(hub), args) + end + + defp try_do_update_room(changeset) do + case changeset do + {:error, reason} -> + {:error, reason} + + %{valid?: false} -> + {:error, + %{ + message: "Changeset errors occurred", + code: :schema_errors, + errors: to_api_errors(changeset) + }} _ -> - case can?(account, update_roles(hub)) do - false -> - {:error, "Account does not have permission to update this hub."} - - true -> - scene_id = Map.get(args, :scene_id, nil) - scene_or_scene_listing = Hub.get_scene_or_scene_listing_by_id(scene_id) - - if scene_id && is_nil(scene_or_scene_listing) do - {:error, "Cannot find scene with id " <> scene_id} - else - changeset = - if scene_or_scene_listing, - do: Hub.changeset_for_new_scene(hub, scene_or_scene_listing), - else: change(hub) - - changeset = Hub.add_attrs_to_changeset(changeset, args) - - # TODO: Member permissions # |> Hub.add_member_permissions_to_changeset(args) - # TODO: Promotions # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) - - case changeset do - %{valid?: false} -> - {:error, - %{ - message: "Changeset errors occurred", - code: :schema_errors, - errors: to_api_errors(changeset) - }} - - _ -> - updated_hub = - changeset - |> Repo.update!() - |> Repo.preload(Hub.hub_preloads()) - - # TODO: Broadcast changes on hub_channel so room occupants know about changes - {:ok, updated_hub} - end - end - end + updated_hub = + changeset + |> Repo.update!() + |> Repo.preload(Hub.hub_preloads()) + + # TODO: Broadcast changes on hub_channel so room occupants know about changes + {:ok, updated_hub} end end end diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 3f007d2a3..c448100f8 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -103,8 +103,9 @@ defmodule RetWeb.Schema.RoomTypes do resolve(&Resolvers.RoomResolver.create_room/3) end + field :update_room, :room do - arg(:id, :string) + arg(:id, non_null(:string)) arg(:name, :string) arg(:description, :string) arg(:room_size, :integer) From 4afe9d6443d67e6ba6ffc3dfba8bf599480a16b6 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 13:56:01 -0700 Subject: [PATCH 028/138] Broadcast changes to anyone connected to the hub channel --- lib/ret_web/resolvers/room_resolver.ex | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index cf8eda29b..a5e86c2d1 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,5 +1,6 @@ defmodule RetWeb.Resolvers.RoomResolver do alias Ret.{Hub, Repo, Scene, SceneListing} + alias RetWeb.Api.V1.{HubView} import Canada, only: [can?: 2] import RetWeb.ApiEctoErrorHelpers import Ecto.Changeset, only: [put_assoc: 3, change: 1] @@ -96,7 +97,7 @@ defmodule RetWeb.Resolvers.RoomResolver do true -> # TODO: Member permissions # |> Hub.add_member_permissions_to_changeset(args) # TODO: Promotions # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) - try_do_update_room(changeset_for_update_room(hub, args)) + try_do_update_room(hub, changeset_for_update_room(hub, args), account) end end @@ -115,7 +116,7 @@ defmodule RetWeb.Resolvers.RoomResolver do Hub.add_attrs_to_changeset(change(hub), args) end - defp try_do_update_room(changeset) do + defp try_do_update_room(hub, changeset, account) do case changeset do {:error, reason} -> {:error, reason} @@ -134,7 +135,24 @@ defmodule RetWeb.Resolvers.RoomResolver do |> Repo.update!() |> Repo.preload(Hub.hub_preloads()) - # TODO: Broadcast changes on hub_channel so room occupants know about changes + updated_hub = Ret.Repo.preload(updated_hub, Hub.hub_preloads()) + + response = + HubView.render("show.json", %{ + hub: updated_hub, + embeddable: account |> can?(embed_hub(updated_hub)) + }) + |> Map.put(:stale_fields, [ + "name", + "description", + "member_permissions", + "room_size", + "allow_promotion", + "scene" + ]) + + RetWeb.Endpoint.broadcast!("hub:" <> updated_hub.hub_sid, "hub_refresh_by_api", response) + {:ok, updated_hub} end end From 5a818956155d32a792f09419bc13ad250d8baf91 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 16:00:00 -0700 Subject: [PATCH 029/138] Add ability to update member_permissions --- lib/ret/hub.ex | 30 +++++++++++++++++-- .../controllers/api/v1/hub_controller.ex | 14 ++------- lib/ret_web/resolvers/room_resolver.ex | 25 +++++++++------- lib/ret_web/schema/room_types.ex | 11 ++++++- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 047f4e422..053e8fd3c 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -153,6 +153,7 @@ defmodule Ret.Hub do def get_scene_or_scene_listing_by_id(nil) do SceneListing.get_random_default_scene_listing() end + def get_scene_or_scene_listing_by_id(id) do Scene.scene_or_scene_listing_by_sid(id) end @@ -230,9 +231,9 @@ defmodule Ret.Hub do attrs["member_permissions"] |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) |> member_permissions_to_int end - def add_member_permissions_update_to_changeset(changeset, hub, attrs) do + defp add_member_permissions_update_to_changeset(changeset, hub, member_permissions) do member_permissions = - Map.merge(member_permissions_for_hub(hub), attrs["member_permissions"]) + Map.merge(member_permissions_for_hub(hub), member_permissions) |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) |> member_permissions_to_int @@ -597,6 +598,31 @@ defmodule Ret.Hub do |> BitFieldUtils.permissions_to_map(@member_permissions) end + def maybe_add_member_permissions(changeset, hub, %{"member_permissions" => member_permissions} = hub_params) do + add_member_permissions_update_to_changeset( + changeset, + hub, + member_permissions + ) + end + + def maybe_add_member_permissions(changeset, hub, %{:member_permissions => member_permissions} = hub_params) do + add_member_permissions_update_to_changeset( + changeset, + hub, + Map.new(member_permissions, fn {k, v} -> {Atom.to_string(k), v} end) + ) + end + + def maybe_add_member_permissions(changeset, _hub, _params) do + changeset + end + + def maybe_add_promotion(changeset, account, hub, %{"allow_promotion" => _} = hub_params), + do: changeset |> Hub.maybe_add_promotion_to_changeset(account, hub, hub_params) + + def maybe_add_promotion(changeset, _account, _hub, _), do: changeset + # The account argument here can be a Ret.Account, a Ret.OAuthProvider or nil. def perms_for_account(%Ret.Hub{} = hub, account) do %{ diff --git a/lib/ret_web/controllers/api/v1/hub_controller.ex b/lib/ret_web/controllers/api/v1/hub_controller.ex index f9ae36661..c873a85b3 100644 --- a/lib/ret_web/controllers/api/v1/hub_controller.ex +++ b/lib/ret_web/controllers/api/v1/hub_controller.ex @@ -68,8 +68,8 @@ defmodule RetWeb.Api.V1.HubController do hub |> Hub.add_attrs_to_changeset(hub_params) |> maybe_add_new_scene(scene) - |> maybe_add_member_permissions(hub, hub_params) - |> maybe_add_promotion(account, hub, hub_params) + |> Hub.maybe_add_member_permissions(hub, hub_params) + |> Hub.maybe_add_promotion(account, hub, hub_params) hub = changeset |> Repo.update!() |> Repo.preload(Hub.hub_preloads()) @@ -80,16 +80,6 @@ defmodule RetWeb.Api.V1.HubController do defp maybe_add_new_scene(changeset, scene), do: changeset |> Hub.add_new_scene_to_changeset(scene) - defp maybe_add_member_permissions(changeset, hub, %{"member_permissions" => %{}} = hub_params), - do: changeset |> Hub.add_member_permissions_update_to_changeset(hub, hub_params) - - defp maybe_add_member_permissions(changeset, _hub, _), do: changeset - - defp maybe_add_promotion(changeset, account, hub, %{"allow_promotion" => _} = hub_params), - do: changeset |> Hub.maybe_add_promotion_to_changeset(account, hub, hub_params) - - defp maybe_add_promotion(changeset, _account, _hub, _), do: changeset - def delete(conn, %{"id" => hub_sid}) do Hub |> Repo.get_by(hub_sid: hub_sid) diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index a5e86c2d1..61799645d 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -95,25 +95,29 @@ defmodule RetWeb.Resolvers.RoomResolver do {:error, "Account does not have permission to update this hub."} true -> - # TODO: Member permissions # |> Hub.add_member_permissions_to_changeset(args) - # TODO: Promotions # |> Hub.maybe_add_promotion_to_changeset(account, hub, args) - try_do_update_room(hub, changeset_for_update_room(hub, args), account) + changeset = + hub + |> Hub.add_attrs_to_changeset(args) + |> Hub.maybe_add_member_permissions(hub, args) + |> Hub.maybe_add_promotion_to_changeset(account, hub, args) + |> maybe_add_new_scene_to_changeset(args) + + try_do_update_room(hub, changeset, account) end end - defp changeset_for_update_room(hub, %{scene_id: scene_id} = args) do + defp maybe_add_new_scene_to_changeset(changeset, %{scene_id: scene_id}) do scene_or_scene_listing = Hub.get_scene_or_scene_listing_by_id(scene_id) if is_nil(scene_or_scene_listing) do {:error, "Cannot find scene with id " <> scene_id} else - Hub.changeset_for_new_scene(hub, scene_or_scene_listing) - |> Hub.add_attrs_to_changeset(args) + Hub.add_new_scene_to_changeset(changeset, scene_or_scene_listing) end end - defp changeset_for_update_room(hub, args) do - Hub.add_attrs_to_changeset(change(hub), args) + defp maybe_add_new_scene_to_changeset(changeset, _args) do + changeset end defp try_do_update_room(hub, changeset, account) do @@ -135,8 +139,6 @@ defmodule RetWeb.Resolvers.RoomResolver do |> Repo.update!() |> Repo.preload(Hub.hub_preloads()) - updated_hub = Ret.Repo.preload(updated_hub, Hub.hub_preloads()) - response = HubView.render("show.json", %{ hub: updated_hub, @@ -148,7 +150,8 @@ defmodule RetWeb.Resolvers.RoomResolver do "member_permissions", "room_size", "allow_promotion", - "scene" + "scene", + "member_permissions" ]) RetWeb.Endpoint.broadcast!("hub:" <> updated_hub.hub_sid, "hub_refresh_by_api", response) diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index c448100f8..0d849164a 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -15,6 +15,15 @@ defmodule RetWeb.Schema.RoomTypes do field(:username, :string) end + input_object :input_member_permissions do + field(:spawn_and_move_media, :boolean) + field(:spawn_camera, :boolean) + field(:spawn_drawing, :boolean) + field(:pin_objects, :boolean) + field(:spawn_emoji, :boolean) + field(:fly, :boolean) + end + object :member_permissions do field(:spawn_and_move_media, :boolean) field(:spawn_camera, :boolean) @@ -110,9 +119,9 @@ defmodule RetWeb.Schema.RoomTypes do arg(:description, :string) arg(:room_size, :integer) arg(:scene_id, :string) + arg(:member_permissions, :input_member_permissions) # TODO: promotion # TODO: add/remove owner - # TODO: member_permissions resolve(&Resolvers.RoomResolver.update_room/3) end From 81726e77b290faf4dbca1d8a3c980fd3d75acb33 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 16:04:45 -0700 Subject: [PATCH 030/138] Remove unused vars --- lib/ret/hub.ex | 4 ++-- lib/ret_web/resolvers/room_resolver.ex | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 053e8fd3c..8471369d7 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -598,7 +598,7 @@ defmodule Ret.Hub do |> BitFieldUtils.permissions_to_map(@member_permissions) end - def maybe_add_member_permissions(changeset, hub, %{"member_permissions" => member_permissions} = hub_params) do + def maybe_add_member_permissions(changeset, hub, %{"member_permissions" => member_permissions}) do add_member_permissions_update_to_changeset( changeset, hub, @@ -606,7 +606,7 @@ defmodule Ret.Hub do ) end - def maybe_add_member_permissions(changeset, hub, %{:member_permissions => member_permissions} = hub_params) do + def maybe_add_member_permissions(changeset, hub, %{:member_permissions => member_permissions}) do add_member_permissions_update_to_changeset( changeset, hub, diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 61799645d..6b39ab6e3 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -1,9 +1,8 @@ defmodule RetWeb.Resolvers.RoomResolver do - alias Ret.{Hub, Repo, Scene, SceneListing} + alias Ret.{Hub, Repo} alias RetWeb.Api.V1.{HubView} import Canada, only: [can?: 2] import RetWeb.ApiEctoErrorHelpers - import Ecto.Changeset, only: [put_assoc: 3, change: 1] def my_rooms(_parent, args, %{context: %{account: account}}) do {:ok, Hub.get_my_rooms(account, args)} @@ -102,7 +101,7 @@ defmodule RetWeb.Resolvers.RoomResolver do |> Hub.maybe_add_promotion_to_changeset(account, hub, args) |> maybe_add_new_scene_to_changeset(args) - try_do_update_room(hub, changeset, account) + try_do_update_room(changeset, account) end end @@ -120,7 +119,7 @@ defmodule RetWeb.Resolvers.RoomResolver do changeset end - defp try_do_update_room(hub, changeset, account) do + defp try_do_update_room(changeset, account) do case changeset do {:error, reason} -> {:error, reason} From 15d6475976c5d130fc5b4218ca4773f4b5742557 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 17:24:07 -0700 Subject: [PATCH 031/138] Add some documentation for the API --- guides/api.md | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 guides/api.md diff --git a/guides/api.md b/guides/api.md new file mode 100644 index 000000000..b66ad3062 --- /dev/null +++ b/guides/api.md @@ -0,0 +1,237 @@ +# Overview +Reticulum includes a [GraphQL](https://graphql.org/) API to better allow you to customize the app to your specific needs. + +## Accessing the API +The API can be accessed by sending `GET` or `POST` requests to `/api/v2/`. +Requests can be sent in code with an `HTTP` client library, on the command line with a tool like `curl`, with a GraphQL-specific client library, or any other tool that speaks `HTTP`. There is also an interactive GUI for accessing the API available at `/api/v2/graphiql`. + +## Authenticating requests +Most requests sent to the API need to be authenticated. To authenticate a request, add the http header `Authorization` with value `Bearer: `. Currently, your API token is the same as your account token, which you can find with the following steps: +- Navigate to the homepage +- Sign in +- Open the developer console of your browser. ( +Instructions for opening the console in firefox: https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Opening_the_Web_Console +Instructions for chrome: https://developers.google.com/web/tools/chrome-devtools/open) +- Type `window.APP.store.state.credentials.token` into the console and press enter. +- Your token should be returned surrounded by quotations marks (`""`) + +It is likely that the authentication method will change in future releases of the API to include something like API tokens whose permissions can be limited to specific scopes, so that people are not encouraged to share admin account tokens. Sharing account tokens is dangerous - don't do it. + +## Passing arguments +We use a library called [`absinthe`](http://absinthe-graphql.org/) to power the `GraphQL` API. This library automatically converts between `camelCase` (a typical convention in `javascript`) and `snake_case` (a typical convention in `elixir`). For this reason, you will send and receive arguments and values in `camelCase`, but will see the corresponding values in `elixir` code as `snake_case`. + +## Rooms +The following examples show the capabilities of creating, querying, and modifying rooms. The code for these commands and object types can be found in [`/lib/ret_web/schema/room_types.ex`](./lib/ret_web/schema/room_types.ex) + +### Create a room +Request: +``` +mutation { + createRoom(name:"My Fun Get-Together"){ + id + } +} +``` +Response: +```js +{ + "data": { + "createRoom": { + "id": "3FqxixG" + } + } +} +``` + +### Querying Rooms +Room queries return a `RoomList` object, which paginates responses. For a specific page or page size, pass the `page` or `pageSize` arguments along with the request. + +#### My rooms +Request: +``` +query { + myRooms(page: 1, pageSize: 10) { + entries { + name, + id, + scene { + ... on Scene { + id, + name + } + ... on SceneListing{ + id, + name + } + } + } + } +} +``` +Response: +```js +{ + "data": { + "myRooms": { + "entries": [ + { + "id": "3FqxixG", + "name": "My Fun Get-Together", + "scene": null + }, + { + "id": "FmNKVjL", + "name": "Foo", + "scene": { + "id": "tXkCgJw", + "name": "Crater 2" + } + }, + "scene": { + "id": "74VD2Et", + "name": "Crater" + } + ] + } + } +} +``` + +#### Query my favorite rooms +Request: +``` +query { + myFavorites { + entries { + name, + id + } + } +} +``` +Response: +```js +{ + "data": { + "favoriteRooms": { + "entries": [ + { + "id": "4jByd2w", + "name": "Uniform Ready Social" + }, + { + "id": "5wQhhbG", + "name": "Angelic Vibrant Spot" + }, + { + "id": "RmNv2k2", + "name": "Golden Perfect Volume" + }, + ] + } + } +} +``` + + +#### Query public rooms +Request: +``` +query { + publicRooms { + entries { + name, + id + } + } +} +``` +Response: +```js +{ + "data": { + "publicRooms": { + "entries": [ + { + "id": "z7LQiNi", + "name": "Big Time Room" + }, + { + "id": "SVnhCWq", + "name": "sdafasdf" + } + ] + } + } +} +``` + +### Updating rooms +#### Set room properties like `name`, `description`, and `roomSize` +``` +mutation { + updateRoom( + id:"FmNKVjL", + name:"Foo bar baz", + description:"Some description", + roomSize:15, + ) { + id + } +} +``` +#### Change the scene of a given room: +``` +mutation { + updateRoom( + id:"FmNKVjL", + sceneId: "74VD2Et", + ) { + id + } +} +``` + +#### Change member permissions in the room: +``` +mutation { + updateRoom( + id:"FmNKVjL", + memberPermissions: { + fly: true, + spawnEmoji: true, + spawnDrawing: true, + pin_objects: false, + spawnCamera: false, + spawnAndMoveMedia: true + } + ) { + id + } +} +``` +### Change everything all in one go: + +``` +mutation { + updateRoom( + id:"FmNKVjL", + name:"Foo bar baz", + description:"Some description", + roomSize:15, + sceneId: "74VD2Et", + #sceneId: "tXkCgJw" + memberPermissions: { + fly: true, + spawnEmoji: true, + spawnDrawing: true, + spawnCamera: false, + spawnAndMoveMedia: true + } + ) { + id + } +} +``` + + From c5e89ebdb32bf26c24c16c1886a5da7a2fd2b656 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 17:27:27 -0700 Subject: [PATCH 032/138] Fixup doc --- guides/api.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guides/api.md b/guides/api.md index b66ad3062..f33dbb8b8 100644 --- a/guides/api.md +++ b/guides/api.md @@ -21,7 +21,7 @@ It is likely that the authentication method will change in future releases of th We use a library called [`absinthe`](http://absinthe-graphql.org/) to power the `GraphQL` API. This library automatically converts between `camelCase` (a typical convention in `javascript`) and `snake_case` (a typical convention in `elixir`). For this reason, you will send and receive arguments and values in `camelCase`, but will see the corresponding values in `elixir` code as `snake_case`. ## Rooms -The following examples show the capabilities of creating, querying, and modifying rooms. The code for these commands and object types can be found in [`/lib/ret_web/schema/room_types.ex`](./lib/ret_web/schema/room_types.ex) +The following examples show the capabilities of creating, querying, and modifying rooms. The code for these commands and object types can be found in [`/lib/ret_web/schema/room_types.ex`](../lib/ret_web/schema/room_types.ex) ### Create a room Request: @@ -201,7 +201,7 @@ mutation { fly: true, spawnEmoji: true, spawnDrawing: true, - pin_objects: false, + pinObjects: false, spawnCamera: false, spawnAndMoveMedia: true } @@ -220,11 +220,11 @@ mutation { description:"Some description", roomSize:15, sceneId: "74VD2Et", - #sceneId: "tXkCgJw" - memberPermissions: { + memberPermissions: { fly: true, spawnEmoji: true, spawnDrawing: true, + pinObjects: false, spawnCamera: false, spawnAndMoveMedia: true } From 5dc75b3d03b955d74690f66a89f4fb9780b4a04e Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Mon, 24 Aug 2020 19:37:39 -0700 Subject: [PATCH 033/138] Add ability to modify allow_promotion --- lib/ret/hub.ex | 5 ++++- lib/ret_web/resolvers/room_resolver.ex | 2 +- lib/ret_web/schema/room_types.ex | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ret/hub.ex b/lib/ret/hub.ex index 8471369d7..310ce1316 100644 --- a/lib/ret/hub.ex +++ b/lib/ret/hub.ex @@ -254,7 +254,7 @@ defmodule Ret.Hub do end def add_promotion_to_changeset(changeset, attrs) do - changeset |> put_change(:allow_promotion, !!attrs["allow_promotion"]) + changeset |> put_change(:allow_promotion, !!attrs["allow_promotion"] || !!attrs.allow_promotion) end def changeset_for_new_seen_occupant_count(%Hub{} = hub, occupant_count) do @@ -621,6 +621,9 @@ defmodule Ret.Hub do def maybe_add_promotion(changeset, account, hub, %{"allow_promotion" => _} = hub_params), do: changeset |> Hub.maybe_add_promotion_to_changeset(account, hub, hub_params) + def maybe_add_promotion(changeset, account, hub, %{allow_promotion: _} = hub_params), + do: changeset |> Hub.maybe_add_promotion_to_changeset(account, hub, hub_params) + def maybe_add_promotion(changeset, _account, _hub, _), do: changeset # The account argument here can be a Ret.Account, a Ret.OAuthProvider or nil. diff --git a/lib/ret_web/resolvers/room_resolver.ex b/lib/ret_web/resolvers/room_resolver.ex index 6b39ab6e3..c4badc267 100644 --- a/lib/ret_web/resolvers/room_resolver.ex +++ b/lib/ret_web/resolvers/room_resolver.ex @@ -98,7 +98,7 @@ defmodule RetWeb.Resolvers.RoomResolver do hub |> Hub.add_attrs_to_changeset(args) |> Hub.maybe_add_member_permissions(hub, args) - |> Hub.maybe_add_promotion_to_changeset(account, hub, args) + |> Hub.maybe_add_promotion(account, hub, args) |> maybe_add_new_scene_to_changeset(args) try_do_update_room(changeset, account) diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 0d849164a..9f968ad7d 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -120,7 +120,7 @@ defmodule RetWeb.Schema.RoomTypes do arg(:room_size, :integer) arg(:scene_id, :string) arg(:member_permissions, :input_member_permissions) - # TODO: promotion + arg(:allow_promotion, :boolean) # TODO: add/remove owner resolve(&Resolvers.RoomResolver.update_room/3) From 57a052f2ee6d088689ffd62a84783150dbcc6a27 Mon Sep 17 00:00:00 2001 From: John Shaughnessy Date: Wed, 26 Aug 2020 12:20:12 -0700 Subject: [PATCH 034/138] Add descriptions to graphql objects/fields --- lib/ret_web/schema/room_types.ex | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/lib/ret_web/schema/room_types.ex b/lib/ret_web/schema/room_types.ex index 9f968ad7d..23490912d 100644 --- a/lib/ret_web/schema/room_types.ex +++ b/lib/ret_web/schema/room_types.ex @@ -4,73 +4,114 @@ defmodule RetWeb.Schema.RoomTypes do alias Ret.Scene import Absinthe.Resolution.Helpers, only: [dataloader: 1] + @desc "Public TLS port number used for TURN" object :turn_transport do + @desc "Public TLS port number used for TURN" field(:port, :integer) end + @desc "TURN information for DLTS over TURN fallback, when enabled" object :turn_info do + @desc "Cryptographic credential, good for two minutes" field(:credential, :string) + @desc "Whether TURN is enabled/configured" field(:enabled, :boolean) + @desc "List of public TLS ports" field(:transports, list_of(:turn_transport)) + @desc "Username, good for two minutes" field(:username, :string) end + @desc "Permissions for participants in the room" input_object :input_member_permissions do + @desc "Allows non-admin participants to spawn and move media" field(:spawn_and_move_media, :boolean) + @desc "Allows non-admin participants to spawn in-game cameras" field(:spawn_camera, :boolean) + @desc "Allows non-admin participants to draw with a pen" field(:spawn_drawing, :boolean) + @desc "Allows non-admin participants to pin media to the room" field(:pin_objects, :boolean) + @desc "Allows non-admin participants to spawn emoji" field(:spawn_emoji, :boolean) + @desc "Allows non-admin participants to toggle fly mode" field(:fly, :boolean) end + @desc "Permissions for participants in the room" object :member_permissions do + @desc "Allows non-admin participants to spawn and move media" field(:spawn_and_move_media, :boolean) + @desc "Allows non-admin participants to spawn in-game cameras" field(:spawn_camera, :boolean) + @desc "Allows non-admin participants to draw with a pen" field(:spawn_drawing, :boolean) + @desc "Allows non-admin participants to pin media to the room" field(:pin_objects, :boolean) + @desc "Allows non-admin participants to spawn emoji" field(:spawn_emoji, :boolean) + @desc "Allows non-admin participants to toggle fly mode" field(:fly, :boolean) end + @desc "A room" object :room do + @desc "The room's unique ID" field(:hub_sid, :id, name: "id") + @desc "The room's name" field(:name, :string) + @desc "The room's name as it appears at the end of its URL" field(:slug, :string) + @desc "A description of the room" field(:description, :string) + @desc "Makes this room public, while it is still open" field(:allow_promotion, :boolean) + @desc "Temporary entry code" field(:entry_code, :string) + @desc "Whether the room is open or closed" field(:entry_mode, :string) + @desc "The host server associated with this room via the load balancer" field(:host, :string) + @desc "The port number used to connect to the host server" field(:port, :integer) do + @desc "The port number used to connect to the host server" resolve(&Resolvers.RoomResolver.port/3) end + @desc "TURN information for DLTS over TURN fallback, when enabled" field(:turn, :turn_info) do resolve(&Resolvers.RoomResolver.turn/3) end + @desc """ + Can be used to remove the X-Frame-Options header that is usually served to the Hubs client when this room is loaded, so that the client can access this room from a ,