From 48ec603140bdad22990680be7c83c4b9e4620df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:24:02 +0100 Subject: [PATCH 01/19] Add Absinthe dependency --- .formatter.exs | 9 +++++++-- mix.exs | 2 ++ mix.lock | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index 7e4d805..51da9f5 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,6 +1,11 @@ [ import_deps: [:ecto, :ecto_sql, :phoenix], subdirectories: ["priv/*/migrations"], - plugins: [Phoenix.LiveView.HTMLFormatter], - inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}", "priv/*/seeds.exs"] + plugins: [Phoenix.LiveView.HTMLFormatter, Absinthe.Formatter], + inputs: [ + "*.{ex,exs}", + "{config,lib,test}/**/*.{ex,exs}", + "priv/*/seeds.exs", + "{lib,priv}/**/*.{gql,graphql}" + ] ] diff --git a/mix.exs b/mix.exs index a5bce50..a3b09bb 100644 --- a/mix.exs +++ b/mix.exs @@ -32,6 +32,8 @@ defmodule SWAPI.MixProject do # Type `mix help deps` for examples and options. defp deps do [ + {:absinthe, "~> 1.7.0"}, + {:absinthe_plug, "~> 1.5"}, {:phoenix, "~> 1.7.9"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.10"}, diff --git a/mix.lock b/mix.lock index 85630fd..a8b711b 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,6 @@ %{ + "absinthe": {:hex, :absinthe, "1.7.6", "0b897365f98d068cfcb4533c0200a8e58825a4aeeae6ec33633ebed6de11773b", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7626951ca5eec627da960615b51009f3a774765406ff02722b1d818f17e5778"}, + "absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"}, From 5cb38e8fd850fc21b3931b23d334f424a807337a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:30:29 +0100 Subject: [PATCH 02/19] Add GraphQL schema, Film type and queries --- lib/swapi_web/graphql/queries.ex | 9 ++++ lib/swapi_web/graphql/queries/film_queries.ex | 18 +++++++ .../graphql/resolvers/film_resolver.ex | 20 ++++++++ lib/swapi_web/graphql/schema.ex | 10 ++++ lib/swapi_web/graphql/types.ex | 7 +++ lib/swapi_web/graphql/types/film.ex | 50 +++++++++++++++++++ lib/swapi_web/router.ex | 10 ++++ 7 files changed, 124 insertions(+) create mode 100644 lib/swapi_web/graphql/queries.ex create mode 100644 lib/swapi_web/graphql/queries/film_queries.ex create mode 100644 lib/swapi_web/graphql/resolvers/film_resolver.ex create mode 100644 lib/swapi_web/graphql/schema.ex create mode 100644 lib/swapi_web/graphql/types.ex create mode 100644 lib/swapi_web/graphql/types/film.ex diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex new file mode 100644 index 0000000..22279d4 --- /dev/null +++ b/lib/swapi_web/graphql/queries.ex @@ -0,0 +1,9 @@ +defmodule SWAPIWeb.GraphQL.Queries do + use Absinthe.Schema.Notation + + import_types(SWAPIWeb.GraphQL.Queries.FilmQueries) + + object :queries do + import_fields(:film_queries) + end +end diff --git a/lib/swapi_web/graphql/queries/film_queries.ex b/lib/swapi_web/graphql/queries/film_queries.ex new file mode 100644 index 0000000..b849286 --- /dev/null +++ b/lib/swapi_web/graphql/queries/film_queries.ex @@ -0,0 +1,18 @@ +defmodule SWAPIWeb.GraphQL.Queries.FilmQueries do + use Absinthe.Schema.Notation + + object :film_queries do + @desc "Get all films." + field :all_films, list_of(:film) do + resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.all/2) + end + + @desc "Get a film by ID." + field :film, :film do + @desc "The ID of the film." + arg(:id, non_null(:id)) + + resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.one/2) + end + end +end diff --git a/lib/swapi_web/graphql/resolvers/film_resolver.ex b/lib/swapi_web/graphql/resolvers/film_resolver.ex new file mode 100644 index 0000000..220decd --- /dev/null +++ b/lib/swapi_web/graphql/resolvers/film_resolver.ex @@ -0,0 +1,20 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.FilmResolver do + @moduledoc """ + Film resolver. + """ + + alias SWAPI.Films + + @spec all(map, map) :: {:ok, list(Film.t())} | {:error, any} + def all(_args, _info) do + {:ok, Films.list_films()} + end + + @spec one(map, Absinthe.Blueprint.t()) :: {:ok, Film.t()} | {:error, any} + def one(%{id: id}, _info) do + case Films.get_film(id) do + {:ok, film} -> {:ok, film} + {:error, :not_found} -> {:error, "Film not found"} + end + end +end diff --git a/lib/swapi_web/graphql/schema.ex b/lib/swapi_web/graphql/schema.ex new file mode 100644 index 0000000..06a3f1a --- /dev/null +++ b/lib/swapi_web/graphql/schema.ex @@ -0,0 +1,10 @@ +defmodule SWAPIWeb.GraphQL.Schema do + use Absinthe.Schema + + import_types(SWAPIWeb.GraphQL.Types) + import_types(SWAPIWeb.GraphQL.Queries) + + query do + import_fields(:queries) + end +end diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex new file mode 100644 index 0000000..e3b836d --- /dev/null +++ b/lib/swapi_web/graphql/types.ex @@ -0,0 +1,7 @@ +defmodule SWAPIWeb.GraphQL.Types do + use Absinthe.Schema.Notation + + import_types(Absinthe.Type.Custom) + + import_types(SWAPIWeb.GraphQL.Types.Film) +end diff --git a/lib/swapi_web/graphql/types/film.ex b/lib/swapi_web/graphql/types/film.ex new file mode 100644 index 0000000..e5a8d2c --- /dev/null +++ b/lib/swapi_web/graphql/types/film.ex @@ -0,0 +1,50 @@ +defmodule SWAPIWeb.GraphQL.Types.Film do + use Absinthe.Schema.Notation + + import Absinthe.Resolution.Helpers + + @desc "The Film type represents a single film." + object :film do + @desc "A unique ID for this film." + field :id, :id + + @desc "The title of this film." + field :title, :string + + @desc "The episode number of this film." + field :episode_id, :integer + + @desc "The opening paragraphs at the beginning of this film." + field :opening_crawl, :string + + @desc "The name of the director of this film." + field :director, :string + + @desc "The name(s) of the producer(s) of this film. Comma separated." + field :producer, :string + + @desc "The date of film release at original creator country." + field :release_date, :date + + @desc "A list of species that are in this film." + field :species, list_of(:species) + + @desc "A list of starships that are in this film." + field :starships, list_of(:starship) + + @desc "A list of vehicles that are in this film." + field :vehicles, list_of(:vehicle) + + @desc "A list of people that are in this film." + field :characters, list_of(:person) + + @desc "A list of planets that are in this film." + field :planets, list_of(:planet) + + @desc "The time that this resource was created." + field :created, :datetime + + @desc "The time that this resource was edited." + field :edited, :datetime + end +end diff --git a/lib/swapi_web/router.ex b/lib/swapi_web/router.ex index 01c226f..3c05991 100644 --- a/lib/swapi_web/router.ex +++ b/lib/swapi_web/router.ex @@ -19,6 +19,10 @@ defmodule SWAPIWeb.Router do plug OpenApiSpex.Plug.PutApiSpec, module: SWAPIWeb.ApiSpec end + pipeline :graphql do + plug :accepts, ["json", "graphql-response+json"] + end + pipeline :browser do plug :accepts, ["html"] plug :fetch_session @@ -53,6 +57,12 @@ defmodule SWAPIWeb.Router do get "/openapi", OpenApiSpex.Plug.RenderSpec, [] end + scope "/graphql" do + pipe_through :graphql + + forward "/", Absinthe.Plug, schema: SWAPIWeb.GraphQL.Schema + end + # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:swapi, :dev_routes) do # If you want to use the LiveDashboard in production, you should put From 7b3f0827d29b8c3334fcc9b86bcd7ca2cae42292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:33:31 +0100 Subject: [PATCH 03/19] Add Person type and queries --- lib/swapi_web/graphql/queries.ex | 2 + .../graphql/queries/person_queries.ex | 18 ++++++ .../graphql/resolvers/person_resolver.ex | 20 +++++++ lib/swapi_web/graphql/types.ex | 1 + lib/swapi_web/graphql/types/person.ex | 56 +++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 lib/swapi_web/graphql/queries/person_queries.ex create mode 100644 lib/swapi_web/graphql/resolvers/person_resolver.ex create mode 100644 lib/swapi_web/graphql/types/person.ex diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex index 22279d4..3d4e18d 100644 --- a/lib/swapi_web/graphql/queries.ex +++ b/lib/swapi_web/graphql/queries.ex @@ -2,8 +2,10 @@ defmodule SWAPIWeb.GraphQL.Queries do use Absinthe.Schema.Notation import_types(SWAPIWeb.GraphQL.Queries.FilmQueries) + import_types(SWAPIWeb.GraphQL.Queries.PersonQueries) object :queries do import_fields(:film_queries) + import_fields(:person_queries) end end diff --git a/lib/swapi_web/graphql/queries/person_queries.ex b/lib/swapi_web/graphql/queries/person_queries.ex new file mode 100644 index 0000000..e852222 --- /dev/null +++ b/lib/swapi_web/graphql/queries/person_queries.ex @@ -0,0 +1,18 @@ +defmodule SWAPIWeb.GraphQL.Queries.PersonQueries do + use Absinthe.Schema.Notation + + object :person_queries do + @desc "Get all people." + field :all_people, list_of(:person) do + resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.all/2) + end + + @desc "Get a person by ID." + field :person, :person do + @desc "The ID of the person." + arg(:id, non_null(:id)) + + resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.one/2) + end + end +end diff --git a/lib/swapi_web/graphql/resolvers/person_resolver.ex b/lib/swapi_web/graphql/resolvers/person_resolver.ex new file mode 100644 index 0000000..4f55383 --- /dev/null +++ b/lib/swapi_web/graphql/resolvers/person_resolver.ex @@ -0,0 +1,20 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.PersonResolver do + @moduledoc """ + Person resolver. + """ + + alias SWAPI.People + + @spec all(map, map) :: {:ok, list(Person.t())} | {:error, any} + def all(_args, _info) do + {:ok, People.list_people()} + end + + @spec one(map, Absinthe.Resolution.t()) :: {:ok, Person.t()} | {:error, any} + def one(%{id: id}, _info) do + case People.get_person(id) do + {:ok, person} -> {:ok, person} + {:error, :not_found} -> {:error, "Person not found"} + end + end +end diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex index e3b836d..9a1cd9b 100644 --- a/lib/swapi_web/graphql/types.ex +++ b/lib/swapi_web/graphql/types.ex @@ -4,4 +4,5 @@ defmodule SWAPIWeb.GraphQL.Types do import_types(Absinthe.Type.Custom) import_types(SWAPIWeb.GraphQL.Types.Film) + import_types(SWAPIWeb.GraphQL.Types.Person) end diff --git a/lib/swapi_web/graphql/types/person.ex b/lib/swapi_web/graphql/types/person.ex new file mode 100644 index 0000000..664676f --- /dev/null +++ b/lib/swapi_web/graphql/types/person.ex @@ -0,0 +1,56 @@ +defmodule SWAPIWeb.GraphQL.Types.Person do + use Absinthe.Schema.Notation + + import Absinthe.Resolution.Helpers + + @desc "The Person type represents an individual person or character within the Star Wars universe." + object :person do + @desc "A unique ID for this person." + field :id, :id + + @desc "The name of this person." + field :name, :string + + @desc "The birth year of the person, using the in-universe standard of **BBY** or **ABY** - Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is a battle that occurs at the end of Star Wars episode IV: A New Hope." + field :birth_year, :string + + @desc "The eye color of this person. Will be \"unknown\" if not known or \"n/a\" if the person does not have an eye." + field :eye_color, :string + + @desc "The gender of this person. Either \"Male\", \"Female\" or \"unknown\", \"n/a\" if the person does not have a gender." + field :gender, :string + + @desc "The hair color of this person. Will be \"unknown\" if not known or \"n/a\" if the person does not have hair." + field :hair_color, :string + + @desc "The height of the person in centimeters." + field :height, :string + + @desc "The mass of the person in kilograms." + field :mass, :string + + @desc "The skin color of this person." + field :skin_color, :string + + @desc "The planet that this person was born on or inhabits." + field :homeworld, :planet + + @desc "A list of films that this person has been in." + field :films, list_of(:film) + + @desc "A list of species that this person belongs to." + field :species, list_of(:species) + + @desc "A list of starships that this person has piloted." + field :starships, list_of(:starship) + + @desc "A list of vehicles that this person has piloted." + field :vehicles, list_of(:vehicle) + + @desc "The time that this resource was created." + field :created, :datetime + + @desc "The time that this resource was edited." + field :edited, :datetime + end +end From 92a363e8d481daaabf8837fba764567fd6b7532b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:35:18 +0100 Subject: [PATCH 04/19] Add Planet type and queries --- lib/swapi_web/graphql/queries.ex | 2 + .../graphql/queries/planet_queries.ex | 18 +++++++ .../graphql/resolvers/planet_resolver.ex | 20 +++++++ lib/swapi_web/graphql/types.ex | 1 + lib/swapi_web/graphql/types/planet.ex | 53 +++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 lib/swapi_web/graphql/queries/planet_queries.ex create mode 100644 lib/swapi_web/graphql/resolvers/planet_resolver.ex create mode 100644 lib/swapi_web/graphql/types/planet.ex diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex index 3d4e18d..834224d 100644 --- a/lib/swapi_web/graphql/queries.ex +++ b/lib/swapi_web/graphql/queries.ex @@ -3,9 +3,11 @@ defmodule SWAPIWeb.GraphQL.Queries do import_types(SWAPIWeb.GraphQL.Queries.FilmQueries) import_types(SWAPIWeb.GraphQL.Queries.PersonQueries) + import_types(SWAPIWeb.GraphQL.Queries.PlanetQueries) object :queries do import_fields(:film_queries) import_fields(:person_queries) + import_fields(:planet_queries) end end diff --git a/lib/swapi_web/graphql/queries/planet_queries.ex b/lib/swapi_web/graphql/queries/planet_queries.ex new file mode 100644 index 0000000..b76c7ff --- /dev/null +++ b/lib/swapi_web/graphql/queries/planet_queries.ex @@ -0,0 +1,18 @@ +defmodule SWAPIWeb.GraphQL.Queries.PlanetQueries do + use Absinthe.Schema.Notation + + object :planet_queries do + @desc "Get all planets." + field :all_planets, list_of(:planet) do + resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.all/2) + end + + @desc "Get a planet by ID." + field :planet, :planet do + @desc "The ID of the planet." + arg(:id, non_null(:id)) + + resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.one/2) + end + end +end diff --git a/lib/swapi_web/graphql/resolvers/planet_resolver.ex b/lib/swapi_web/graphql/resolvers/planet_resolver.ex new file mode 100644 index 0000000..bba305a --- /dev/null +++ b/lib/swapi_web/graphql/resolvers/planet_resolver.ex @@ -0,0 +1,20 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.PlanetResolver do + @moduledoc """ + Planet resolver. + """ + + alias SWAPI.Planets + + @spec all(map, map) :: {:ok, list(Planet.t())} | {:error, any} + def all(_args, _info) do + {:ok, Planets.list_planets()} + end + + @spec one(map, Absinthe.Resolution.t()) :: {:ok, Planet.t()} | {:error, any} + def one(%{id: id}, _info) do + case Planets.get_planet(id) do + {:ok, planet} -> {:ok, planet} + {:error, :not_found} -> {:error, "Planet not found"} + end + end +end diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex index 9a1cd9b..7ab8582 100644 --- a/lib/swapi_web/graphql/types.ex +++ b/lib/swapi_web/graphql/types.ex @@ -5,4 +5,5 @@ defmodule SWAPIWeb.GraphQL.Types do import_types(SWAPIWeb.GraphQL.Types.Film) import_types(SWAPIWeb.GraphQL.Types.Person) + import_types(SWAPIWeb.GraphQL.Types.Planet) end diff --git a/lib/swapi_web/graphql/types/planet.ex b/lib/swapi_web/graphql/types/planet.ex new file mode 100644 index 0000000..bc5bee4 --- /dev/null +++ b/lib/swapi_web/graphql/types/planet.ex @@ -0,0 +1,53 @@ +defmodule SWAPIWeb.GraphQL.Types.Planet do + use Absinthe.Schema.Notation + + import Absinthe.Resolution.Helpers + + @desc "The Planet type represents a large mass, planet or planetoid in the Star Wars Universe, at the time of 0 ABY." + object :planet do + @desc "A unique ID for this planet." + field :id, :id + + @desc "The name of this planet." + field :name, :string + + @desc "The diameter of this planet in kilometers." + field :diameter, :string + + @desc "The number of standard hours it takes for this planet to complete a single rotation on its axis." + field :rotation_period, :string + + @desc "The number of standard days it takes for this planet to complete a single orbit of its local star." + field :orbital_period, :string + + @desc "A number denoting the gravity of this planet, where \"1\" is normal or 1 standard G. \"2\" is twice or 2 standard Gs. \"0.5\" is half or 0.5 standard Gs." + field :gravity, :string + + @desc "The average population of sentient beings inhabiting this planet." + field :population, :string + + @desc "The climate of this planet. Comma separated if diverse." + field :climate, :string + + @desc "The terrain of this planet. Comma separated if diverse." + field :terrain, :string + + @desc "The percentage of the planet surface that is naturally occurring water or bodies of water." + field :surface_water, :string + + @desc "A list of people that live on this planet." + field :residents, list_of(:person) + + @desc "A list of species that live on this planet." + field :species, list_of(:species) + + @desc "A list of films that this planet has appeared in." + field :films, list_of(:film) + + @desc "The time that this resource was created." + field :created, :datetime + + @desc "The time that this resource was edited." + field :edited, :datetime + end +end From 9b3cf1c8a1747234741793be37d0635db332914a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:36:49 +0100 Subject: [PATCH 05/19] Add Species type and queries --- lib/swapi_web/graphql/queries.ex | 2 + .../graphql/queries/species_queries.ex | 18 +++++++ .../graphql/resolvers/species_resolver.ex | 20 +++++++ lib/swapi_web/graphql/types.ex | 1 + lib/swapi_web/graphql/types/species.ex | 53 +++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 lib/swapi_web/graphql/queries/species_queries.ex create mode 100644 lib/swapi_web/graphql/resolvers/species_resolver.ex create mode 100644 lib/swapi_web/graphql/types/species.ex diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex index 834224d..f97090e 100644 --- a/lib/swapi_web/graphql/queries.ex +++ b/lib/swapi_web/graphql/queries.ex @@ -4,10 +4,12 @@ defmodule SWAPIWeb.GraphQL.Queries do import_types(SWAPIWeb.GraphQL.Queries.FilmQueries) import_types(SWAPIWeb.GraphQL.Queries.PersonQueries) import_types(SWAPIWeb.GraphQL.Queries.PlanetQueries) + import_types(SWAPIWeb.GraphQL.Queries.SpeciesQueries) object :queries do import_fields(:film_queries) import_fields(:person_queries) import_fields(:planet_queries) + import_fields(:species_queries) end end diff --git a/lib/swapi_web/graphql/queries/species_queries.ex b/lib/swapi_web/graphql/queries/species_queries.ex new file mode 100644 index 0000000..47f5353 --- /dev/null +++ b/lib/swapi_web/graphql/queries/species_queries.ex @@ -0,0 +1,18 @@ +defmodule SWAPIWeb.GraphQL.Queries.SpeciesQueries do + use Absinthe.Schema.Notation + + object :species_queries do + @desc "Get all species." + field :all_species, list_of(:species) do + resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.all/2) + end + + @desc "Get a species by ID." + field :species, :species do + @desc "The ID of the species." + arg(:id, non_null(:id)) + + resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.one/2) + end + end +end diff --git a/lib/swapi_web/graphql/resolvers/species_resolver.ex b/lib/swapi_web/graphql/resolvers/species_resolver.ex new file mode 100644 index 0000000..086da75 --- /dev/null +++ b/lib/swapi_web/graphql/resolvers/species_resolver.ex @@ -0,0 +1,20 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.SpeciesResolver do + @moduledoc """ + Species resolver. + """ + + alias SWAPI.Species + + @spec all(map, map) :: {:ok, list(Species.t())} | {:error, any} + def all(_args, _info) do + {:ok, Species.list_species()} + end + + @spec one(map, Absinthe.Resolution.t()) :: {:ok, Species.t()} | {:error, any} + def one(%{id: id}, _info) do + case Species.get_species(id) do + {:ok, species} -> {:ok, species} + {:error, :not_found} -> {:error, "Species not found"} + end + end +end diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex index 7ab8582..00b0a08 100644 --- a/lib/swapi_web/graphql/types.ex +++ b/lib/swapi_web/graphql/types.ex @@ -6,4 +6,5 @@ defmodule SWAPIWeb.GraphQL.Types do import_types(SWAPIWeb.GraphQL.Types.Film) import_types(SWAPIWeb.GraphQL.Types.Person) import_types(SWAPIWeb.GraphQL.Types.Planet) + import_types(SWAPIWeb.GraphQL.Types.Species) end diff --git a/lib/swapi_web/graphql/types/species.ex b/lib/swapi_web/graphql/types/species.ex new file mode 100644 index 0000000..bca9d8f --- /dev/null +++ b/lib/swapi_web/graphql/types/species.ex @@ -0,0 +1,53 @@ +defmodule SWAPIWeb.GraphQL.Types.Species do + use Absinthe.Schema.Notation + + import Absinthe.Resolution.Helpers + + @desc "The Species type represents a type of person or character within the Star Wars Universe." + object :species do + @desc "A unique ID for this species." + field :id, :id + + @desc "The name of this species." + field :name, :string + + @desc "The classification of this species, such as \"mammal\" or \"reptile\"." + field :classification, :string + + @desc "The designation of this species, such as \"sentient\"." + field :designation, :string + + @desc "The average height of this species in centimeters." + field :average_height, :string + + @desc "The average lifespan of this species in years." + field :average_lifespan, :string + + @desc "A comma-separated string of common eye colors for this species, \"none\" if this species does not typically have eyes." + field :eye_colors, :string + + @desc "A comma-separated string of common hair colors for this species, \"none\" if this species does not typically have hair." + field :hair_colors, :string + + @desc "A comma-separated string of common skin colors for this species, \"none\" if this species does not typically have skin." + field :skin_colors, :string + + @desc "The language commonly spoken by this species." + field :language, :string + + @desc "The planet that this species originates from." + field :homeworld, :planet + + @desc "A list of people that are a part of this species." + field :people, list_of(:person) + + @desc "A list of films that this species has appeared in." + field :films, list_of(:film) + + @desc "The time that this resource was created." + field :created, :datetime + + @desc "The time that this resource was edited." + field :edited, :datetime + end +end From 10b5ac3cef85a4478f49e5df904102d931331544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:43:58 +0100 Subject: [PATCH 06/19] Add Starship type and queries --- lib/swapi_web/graphql/queries.ex | 2 + .../graphql/queries/starship_queries.ex | 18 ++++ .../graphql/resolvers/starship_resolver.ex | 20 +++++ lib/swapi_web/graphql/types.ex | 1 + lib/swapi_web/graphql/types/starship.ex | 86 +++++++++++++++++++ lib/swapi_web/graphql/util.ex | 11 +++ 6 files changed, 138 insertions(+) create mode 100644 lib/swapi_web/graphql/queries/starship_queries.ex create mode 100644 lib/swapi_web/graphql/resolvers/starship_resolver.ex create mode 100644 lib/swapi_web/graphql/types/starship.ex create mode 100644 lib/swapi_web/graphql/util.ex diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex index f97090e..6cab91d 100644 --- a/lib/swapi_web/graphql/queries.ex +++ b/lib/swapi_web/graphql/queries.ex @@ -5,11 +5,13 @@ defmodule SWAPIWeb.GraphQL.Queries do import_types(SWAPIWeb.GraphQL.Queries.PersonQueries) import_types(SWAPIWeb.GraphQL.Queries.PlanetQueries) import_types(SWAPIWeb.GraphQL.Queries.SpeciesQueries) + import_types(SWAPIWeb.GraphQL.Queries.StarshipQueries) object :queries do import_fields(:film_queries) import_fields(:person_queries) import_fields(:planet_queries) import_fields(:species_queries) + import_fields(:starship_queries) end end diff --git a/lib/swapi_web/graphql/queries/starship_queries.ex b/lib/swapi_web/graphql/queries/starship_queries.ex new file mode 100644 index 0000000..8c8a899 --- /dev/null +++ b/lib/swapi_web/graphql/queries/starship_queries.ex @@ -0,0 +1,18 @@ +defmodule SWAPIWeb.GraphQL.Queries.StarshipQueries do + use Absinthe.Schema.Notation + + object :starship_queries do + @desc "Get all starships." + field :all_starships, list_of(:starship) do + resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.all/2) + end + + @desc "Get a starship by ID." + field :starship, :starship do + @desc "The ID of the starship." + arg(:id, non_null(:id)) + + resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.one/2) + end + end +end diff --git a/lib/swapi_web/graphql/resolvers/starship_resolver.ex b/lib/swapi_web/graphql/resolvers/starship_resolver.ex new file mode 100644 index 0000000..f6a23a2 --- /dev/null +++ b/lib/swapi_web/graphql/resolvers/starship_resolver.ex @@ -0,0 +1,20 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.StarshipResolver do + @moduledoc """ + Starship resolver. + """ + + alias SWAPI.Starships + + @spec all(map, map) :: {:ok, list(Starship.t())} | {:error, any} + def all(_args, _info) do + {:ok, Starships.list_starships()} + end + + @spec one(map, Absinthe.Resolution.t()) :: {:ok, Starship.t()} | {:error, any} + def one(%{id: id}, _info) do + case Starships.get_starship(id) do + {:ok, starship} -> {:ok, starship} + {:error, :not_found} -> {:error, "Starship not found"} + end + end +end diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex index 00b0a08..0deb357 100644 --- a/lib/swapi_web/graphql/types.ex +++ b/lib/swapi_web/graphql/types.ex @@ -7,4 +7,5 @@ defmodule SWAPIWeb.GraphQL.Types do import_types(SWAPIWeb.GraphQL.Types.Person) import_types(SWAPIWeb.GraphQL.Types.Planet) import_types(SWAPIWeb.GraphQL.Types.Species) + import_types(SWAPIWeb.GraphQL.Types.Starship) end diff --git a/lib/swapi_web/graphql/types/starship.ex b/lib/swapi_web/graphql/types/starship.ex new file mode 100644 index 0000000..b555b1e --- /dev/null +++ b/lib/swapi_web/graphql/types/starship.ex @@ -0,0 +1,86 @@ +defmodule SWAPIWeb.GraphQL.Types.Starship do + use Absinthe.Schema.Notation + + import SWAPIWeb.GraphQL.Util + + @desc "The Starship type represents a single transport craft that has hyperdrive capability." + object :starship do + @desc "A unique ID for this starship." + field :id, :id + + @desc "The name of this starship. The common name, such as \"Death Star\"." + field :name, :string do + resolve(&transport_field_callback/3) + end + + @desc "The model or official name of this starship. Such as \"T-65 X-wing\" or \"DS-1 Orbital Battle Station\"." + field :model, :string do + resolve(&transport_field_callback/3) + end + + @desc "The class of this starship, such as \"Starfighter\" or \"Deep Space Mobile Battlestation\"" + field :starship_class, :string + + @desc "The manufacturer of this starship. Comma separated if more than one." + field :manufacturer, :string do + resolve(&transport_field_callback/3) + end + + @desc "The cost of this starship new, in galactic credits." + field :cost_in_credits, :string do + resolve(&transport_field_callback/3) + end + + @desc "The length of this starship in meters." + field :length, :string do + resolve(&transport_field_callback/3) + end + + @desc "The number of personnel needed to run or pilot this starship." + field :crew, :string do + resolve(&transport_field_callback/3) + end + + @desc "The number of non-essential people this starship can transport." + field :passengers, :string do + resolve(&transport_field_callback/3) + end + + @desc "The maximum speed of this starship in the atmosphere. \"N/A\" if this starship is incapable of atmospheric flight." + field :max_atmosphering_speed, :string do + resolve(&transport_field_callback/3) + end + + @desc "The class of this starships hyperdrive." + field :hyperdrive_rating, :string + + @desc "The Maximum number of Megalights this starship can travel in a standard hour. A \"Megalight\" is a standard unit of distance and has never been defined before within the Star Wars universe. This figure is only really useful for measuring the difference in speed of starships. We can assume it is similar to AU, the distance between our Sun (Sol) and Earth." + field :mglt, :string + + @desc "The maximum number of kilograms that this starship can transport." + field :cargo_capacity, :string do + resolve(&transport_field_callback/3) + end + + @desc "The maximum length of time that this starship can provide consumables for its entire crew without having to resupply." + field :consumables, :string do + resolve(&transport_field_callback/3) + end + + @desc "A list of films that this starship has appeared in." + field :films, list_of(:film) + + @desc "A list of people that this starship has been piloted by." + field :pilots, list_of(:person) + + @desc "The time that this resource was created." + field :created, :datetime do + resolve(&transport_field_callback/3) + end + + @desc "The time that this resource was edited." + field :edited, :datetime do + resolve(&transport_field_callback/3) + end + end +end diff --git a/lib/swapi_web/graphql/util.ex b/lib/swapi_web/graphql/util.ex new file mode 100644 index 0000000..c7c319a --- /dev/null +++ b/lib/swapi_web/graphql/util.ex @@ -0,0 +1,11 @@ +defmodule SWAPIWeb.GraphQL.Util do + def transport_field_callback(parent, _args, %{path: [field | _]}) do + case Map.get(parent.transport, field.schema_node.identifier) do + nil -> {:error, "Invalid transport field"} + value -> {:ok, value} + end + end + + def transport_field_callback(_parent, _args, _info), + do: {:error, "Invalid transport field"} +end From fa64e864ee4fb4885d69faeb0ea7dabf7c18b85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:46:08 +0100 Subject: [PATCH 07/19] Add Vehicle type and queries --- lib/swapi_web/graphql/queries.ex | 2 + .../graphql/queries/vehicle_queries.ex | 18 +++++ .../graphql/resolvers/vehicle_resolver.ex | 20 +++++ lib/swapi_web/graphql/types.ex | 1 + lib/swapi_web/graphql/types/vehicle.ex | 80 +++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 lib/swapi_web/graphql/queries/vehicle_queries.ex create mode 100644 lib/swapi_web/graphql/resolvers/vehicle_resolver.ex create mode 100644 lib/swapi_web/graphql/types/vehicle.ex diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex index 6cab91d..21b6c41 100644 --- a/lib/swapi_web/graphql/queries.ex +++ b/lib/swapi_web/graphql/queries.ex @@ -6,6 +6,7 @@ defmodule SWAPIWeb.GraphQL.Queries do import_types(SWAPIWeb.GraphQL.Queries.PlanetQueries) import_types(SWAPIWeb.GraphQL.Queries.SpeciesQueries) import_types(SWAPIWeb.GraphQL.Queries.StarshipQueries) + import_types(SWAPIWeb.GraphQL.Queries.VehicleQueries) object :queries do import_fields(:film_queries) @@ -13,5 +14,6 @@ defmodule SWAPIWeb.GraphQL.Queries do import_fields(:planet_queries) import_fields(:species_queries) import_fields(:starship_queries) + import_fields(:vehicle_queries) end end diff --git a/lib/swapi_web/graphql/queries/vehicle_queries.ex b/lib/swapi_web/graphql/queries/vehicle_queries.ex new file mode 100644 index 0000000..275f2a3 --- /dev/null +++ b/lib/swapi_web/graphql/queries/vehicle_queries.ex @@ -0,0 +1,18 @@ +defmodule SWAPIWeb.GraphQL.Queries.VehicleQueries do + use Absinthe.Schema.Notation + + object :vehicle_queries do + @desc "Get all vehicles." + field :all_vehicles, list_of(:vehicle) do + resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.all/2) + end + + @desc "Get a vehicle by ID." + field :vehicle, :vehicle do + @desc "The ID of the vehicle." + arg(:id, non_null(:id)) + + resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.one/2) + end + end +end diff --git a/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex b/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex new file mode 100644 index 0000000..3c3daf5 --- /dev/null +++ b/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex @@ -0,0 +1,20 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.VehicleResolver do + @moduledoc """ + Vehicle resolver. + """ + + alias SWAPI.Vehicles + + @spec all(map, map) :: {:ok, list(Vehicle.t())} | {:error, any} + def all(_args, _info) do + {:ok, Vehicles.list_vehicles()} + end + + @spec one(map, Absinthe.Resolution.t()) :: {:ok, Vehicle.t()} | {:error, any} + def one(%{id: id}, _info) do + case Vehicles.get_vehicle(id) do + {:ok, vehicle} -> {:ok, vehicle} + {:error, :not_found} -> {:error, "Vehicle not found"} + end + end +end diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex index 0deb357..1ff71fb 100644 --- a/lib/swapi_web/graphql/types.ex +++ b/lib/swapi_web/graphql/types.ex @@ -8,4 +8,5 @@ defmodule SWAPIWeb.GraphQL.Types do import_types(SWAPIWeb.GraphQL.Types.Planet) import_types(SWAPIWeb.GraphQL.Types.Species) import_types(SWAPIWeb.GraphQL.Types.Starship) + import_types(SWAPIWeb.GraphQL.Types.Vehicle) end diff --git a/lib/swapi_web/graphql/types/vehicle.ex b/lib/swapi_web/graphql/types/vehicle.ex new file mode 100644 index 0000000..22f66f9 --- /dev/null +++ b/lib/swapi_web/graphql/types/vehicle.ex @@ -0,0 +1,80 @@ +defmodule SWAPIWeb.GraphQL.Types.Vehicle do + use Absinthe.Schema.Notation + + import SWAPIWeb.GraphQL.Util + + @desc "The Vehicle type represents a single transport craft that does not have hyperdrive capability." + object :vehicle do + @desc "A unique ID for this vehicle." + field :id, :id + + @desc "The name of this vehicle. The common name, such as \"Sand Crawler\" or \"Speeder bike\"." + field :name, :string do + resolve(&transport_field_callback/3) + end + + @desc "The model or official name of this vehicle. Such as \"All-Terrain Attack Transport\"." + field :model, :string do + resolve(&transport_field_callback/3) + end + + @desc "The class of this vehicle, such as \"Wheeled\" or \"Repulsorcraft\"." + field :vehicle_class, :string + + @desc "The manufacturer of this vehicle. Comma separated if more than one." + field :manufacturer, :string do + resolve(&transport_field_callback/3) + end + + @desc "The length of this vehicle in meters." + field :cost_in_credits, :string do + resolve(&transport_field_callback/3) + end + + @desc "The cost of this vehicle new, in Galactic Credits." + field :length, :string do + resolve(&transport_field_callback/3) + end + + @desc "The number of personnel needed to run or pilot this vehicle." + field :crew, :string do + resolve(&transport_field_callback/3) + end + + @desc "The number of non-essential people this vehicle can transport." + field :passengers, :string do + resolve(&transport_field_callback/3) + end + + @desc "The maximum speed of this vehicle in the atmosphere." + field :max_atmosphering_speed, :string do + resolve(&transport_field_callback/3) + end + + @desc "The maximum number of kilograms that this vehicle can transport." + field :cargo_capacity, :string do + resolve(&transport_field_callback/3) + end + + @desc "The maximum length of time that this vehicle can provide consumables for its entire crew without having to resupply." + field :consumables, :string do + resolve(&transport_field_callback/3) + end + + @desc "A list of films that this vehicle has appeared in." + field :films, list_of(:film) + + @desc "A list of people that this vehicle has been piloted by." + field :pilots, list_of(:person) + + @desc "The time that this resource was created." + field :created, :datetime do + resolve(&transport_field_callback/3) + end + + @desc "The time that this resource was edited." + field :edited, :datetime do + resolve(&transport_field_callback/3) + end + end +end From b6cd3360837ee9d90a7c677c94fe756d38707220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:52:19 +0100 Subject: [PATCH 08/19] Use dataloader to resolve references --- lib/swapi.ex | 4 +++ lib/swapi_web/graphql/schema.ex | 12 +++++++++ lib/swapi_web/graphql/types/film.ex | 20 +++++++++++---- lib/swapi_web/graphql/types/person.ex | 20 +++++++++++---- lib/swapi_web/graphql/types/planet.ex | 12 ++++++--- lib/swapi_web/graphql/types/species.ex | 12 ++++++--- lib/swapi_web/graphql/types/starship.ex | 33 ++++++++++++++----------- lib/swapi_web/graphql/types/vehicle.ex | 33 ++++++++++++++----------- lib/swapi_web/graphql/util.ex | 6 ++--- mix.exs | 1 + mix.lock | 1 + 11 files changed, 107 insertions(+), 47 deletions(-) diff --git a/lib/swapi.ex b/lib/swapi.ex index 0d29aba..5f99e17 100644 --- a/lib/swapi.ex +++ b/lib/swapi.ex @@ -6,4 +6,8 @@ defmodule SWAPI do Contexts are also responsible for managing your data, regardless if it comes from the database, an external API or others. """ + + def data(), do: Dataloader.Ecto.new(SWAPI.Repo, query: &query/2) + + def query(queryable, _params), do: queryable end diff --git a/lib/swapi_web/graphql/schema.ex b/lib/swapi_web/graphql/schema.ex index 06a3f1a..a88b7e9 100644 --- a/lib/swapi_web/graphql/schema.ex +++ b/lib/swapi_web/graphql/schema.ex @@ -7,4 +7,16 @@ defmodule SWAPIWeb.GraphQL.Schema do query do import_fields(:queries) end + + def context(ctx) do + loader = + Dataloader.new() + |> Dataloader.add_source(SWAPI, SWAPI.data()) + + Map.put(ctx, :loader, loader) + end + + def plugins do + [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() + end end diff --git a/lib/swapi_web/graphql/types/film.ex b/lib/swapi_web/graphql/types/film.ex index e5a8d2c..6b28825 100644 --- a/lib/swapi_web/graphql/types/film.ex +++ b/lib/swapi_web/graphql/types/film.ex @@ -27,19 +27,29 @@ defmodule SWAPIWeb.GraphQL.Types.Film do field :release_date, :date @desc "A list of species that are in this film." - field :species, list_of(:species) + field :species, list_of(:species) do + resolve(dataloader(SWAPI)) + end @desc "A list of starships that are in this film." - field :starships, list_of(:starship) + field :starships, list_of(:starship) do + resolve(dataloader(SWAPI)) + end @desc "A list of vehicles that are in this film." - field :vehicles, list_of(:vehicle) + field :vehicles, list_of(:vehicle) do + resolve(dataloader(SWAPI)) + end @desc "A list of people that are in this film." - field :characters, list_of(:person) + field :characters, list_of(:person) do + resolve(dataloader(SWAPI)) + end @desc "A list of planets that are in this film." - field :planets, list_of(:planet) + field :planets, list_of(:planet) do + resolve(dataloader(SWAPI)) + end @desc "The time that this resource was created." field :created, :datetime diff --git a/lib/swapi_web/graphql/types/person.ex b/lib/swapi_web/graphql/types/person.ex index 664676f..ec7e5c3 100644 --- a/lib/swapi_web/graphql/types/person.ex +++ b/lib/swapi_web/graphql/types/person.ex @@ -33,19 +33,29 @@ defmodule SWAPIWeb.GraphQL.Types.Person do field :skin_color, :string @desc "The planet that this person was born on or inhabits." - field :homeworld, :planet + field :homeworld, :planet do + resolve(dataloader(SWAPI)) + end @desc "A list of films that this person has been in." - field :films, list_of(:film) + field :films, list_of(:film) do + resolve(dataloader(SWAPI)) + end @desc "A list of species that this person belongs to." - field :species, list_of(:species) + field :species, list_of(:species) do + resolve(dataloader(SWAPI)) + end @desc "A list of starships that this person has piloted." - field :starships, list_of(:starship) + field :starships, list_of(:starship) do + resolve(dataloader(SWAPI)) + end @desc "A list of vehicles that this person has piloted." - field :vehicles, list_of(:vehicle) + field :vehicles, list_of(:vehicle) do + resolve(dataloader(SWAPI)) + end @desc "The time that this resource was created." field :created, :datetime diff --git a/lib/swapi_web/graphql/types/planet.ex b/lib/swapi_web/graphql/types/planet.ex index bc5bee4..7f2da4d 100644 --- a/lib/swapi_web/graphql/types/planet.ex +++ b/lib/swapi_web/graphql/types/planet.ex @@ -36,13 +36,19 @@ defmodule SWAPIWeb.GraphQL.Types.Planet do field :surface_water, :string @desc "A list of people that live on this planet." - field :residents, list_of(:person) + field :residents, list_of(:person) do + resolve(dataloader(SWAPI)) + end @desc "A list of species that live on this planet." - field :species, list_of(:species) + field :species, list_of(:species) do + resolve(dataloader(SWAPI)) + end @desc "A list of films that this planet has appeared in." - field :films, list_of(:film) + field :films, list_of(:film) do + resolve(dataloader(SWAPI)) + end @desc "The time that this resource was created." field :created, :datetime diff --git a/lib/swapi_web/graphql/types/species.ex b/lib/swapi_web/graphql/types/species.ex index bca9d8f..83ddfa9 100644 --- a/lib/swapi_web/graphql/types/species.ex +++ b/lib/swapi_web/graphql/types/species.ex @@ -36,13 +36,19 @@ defmodule SWAPIWeb.GraphQL.Types.Species do field :language, :string @desc "The planet that this species originates from." - field :homeworld, :planet + field :homeworld, :planet do + resolve(dataloader(SWAPI)) + end @desc "A list of people that are a part of this species." - field :people, list_of(:person) + field :people, list_of(:person) do + resolve(dataloader(SWAPI)) + end @desc "A list of films that this species has appeared in." - field :films, list_of(:film) + field :films, list_of(:film) do + resolve(dataloader(SWAPI)) + end @desc "The time that this resource was created." field :created, :datetime diff --git a/lib/swapi_web/graphql/types/starship.ex b/lib/swapi_web/graphql/types/starship.ex index b555b1e..7f3a743 100644 --- a/lib/swapi_web/graphql/types/starship.ex +++ b/lib/swapi_web/graphql/types/starship.ex @@ -1,6 +1,7 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do use Absinthe.Schema.Notation + import Absinthe.Resolution.Helpers import SWAPIWeb.GraphQL.Util @desc "The Starship type represents a single transport craft that has hyperdrive capability." @@ -10,12 +11,12 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do @desc "The name of this starship. The common name, such as \"Death Star\"." field :name, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The model or official name of this starship. Such as \"T-65 X-wing\" or \"DS-1 Orbital Battle Station\"." field :model, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The class of this starship, such as \"Starfighter\" or \"Deep Space Mobile Battlestation\"" @@ -23,32 +24,32 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do @desc "The manufacturer of this starship. Comma separated if more than one." field :manufacturer, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The cost of this starship new, in galactic credits." field :cost_in_credits, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The length of this starship in meters." field :length, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The number of personnel needed to run or pilot this starship." field :crew, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The number of non-essential people this starship can transport." field :passengers, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The maximum speed of this starship in the atmosphere. \"N/A\" if this starship is incapable of atmospheric flight." field :max_atmosphering_speed, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The class of this starships hyperdrive." @@ -59,28 +60,32 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do @desc "The maximum number of kilograms that this starship can transport." field :cargo_capacity, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The maximum length of time that this starship can provide consumables for its entire crew without having to resupply." field :consumables, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "A list of films that this starship has appeared in." - field :films, list_of(:film) + field :films, list_of(:film) do + resolve(dataloader(SWAPI)) + end @desc "A list of people that this starship has been piloted by." - field :pilots, list_of(:person) + field :pilots, list_of(:person) do + resolve(dataloader(SWAPI)) + end @desc "The time that this resource was created." field :created, :datetime do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The time that this resource was edited." field :edited, :datetime do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end end end diff --git a/lib/swapi_web/graphql/types/vehicle.ex b/lib/swapi_web/graphql/types/vehicle.ex index 22f66f9..8155459 100644 --- a/lib/swapi_web/graphql/types/vehicle.ex +++ b/lib/swapi_web/graphql/types/vehicle.ex @@ -1,6 +1,7 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do use Absinthe.Schema.Notation + import Absinthe.Resolution.Helpers import SWAPIWeb.GraphQL.Util @desc "The Vehicle type represents a single transport craft that does not have hyperdrive capability." @@ -10,12 +11,12 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do @desc "The name of this vehicle. The common name, such as \"Sand Crawler\" or \"Speeder bike\"." field :name, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The model or official name of this vehicle. Such as \"All-Terrain Attack Transport\"." field :model, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The class of this vehicle, such as \"Wheeled\" or \"Repulsorcraft\"." @@ -23,58 +24,62 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do @desc "The manufacturer of this vehicle. Comma separated if more than one." field :manufacturer, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The length of this vehicle in meters." field :cost_in_credits, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The cost of this vehicle new, in Galactic Credits." field :length, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The number of personnel needed to run or pilot this vehicle." field :crew, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The number of non-essential people this vehicle can transport." field :passengers, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The maximum speed of this vehicle in the atmosphere." field :max_atmosphering_speed, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The maximum number of kilograms that this vehicle can transport." field :cargo_capacity, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The maximum length of time that this vehicle can provide consumables for its entire crew without having to resupply." field :consumables, :string do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "A list of films that this vehicle has appeared in." - field :films, list_of(:film) + field :films, list_of(:film) do + resolve(dataloader(SWAPI)) + end @desc "A list of people that this vehicle has been piloted by." - field :pilots, list_of(:person) + field :pilots, list_of(:person) do + resolve(dataloader(SWAPI)) + end @desc "The time that this resource was created." field :created, :datetime do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end @desc "The time that this resource was edited." field :edited, :datetime do - resolve(&transport_field_callback/3) + resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) end end end diff --git a/lib/swapi_web/graphql/util.ex b/lib/swapi_web/graphql/util.ex index c7c319a..5879d15 100644 --- a/lib/swapi_web/graphql/util.ex +++ b/lib/swapi_web/graphql/util.ex @@ -1,11 +1,11 @@ defmodule SWAPIWeb.GraphQL.Util do - def transport_field_callback(parent, _args, %{path: [field | _]}) do - case Map.get(parent.transport, field.schema_node.identifier) do + def transport_field_callback(transport, _parent, _args, %{path: [field | _]}) do + case Map.get(transport, field.schema_node.identifier) do nil -> {:error, "Invalid transport field"} value -> {:ok, value} end end - def transport_field_callback(_parent, _args, _info), + def transport_field_callback(_transport, _parent, _args, _info), do: {:error, "Invalid transport field"} end diff --git a/mix.exs b/mix.exs index a3b09bb..0afdfe4 100644 --- a/mix.exs +++ b/mix.exs @@ -34,6 +34,7 @@ defmodule SWAPI.MixProject do [ {:absinthe, "~> 1.7.0"}, {:absinthe_plug, "~> 1.5"}, + {:dataloader, "~> 2.0.0"}, {:phoenix, "~> 1.7.9"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.10"}, diff --git a/mix.lock b/mix.lock index a8b711b..24f3edd 100644 --- a/mix.lock +++ b/mix.lock @@ -10,6 +10,7 @@ "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, "dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"}, + "dataloader": {:hex, :dataloader, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dns_cluster": {:hex, :dns_cluster, "0.1.1", "73b4b2c3ec692f8a64276c43f8c929733a9ab9ac48c34e4c0b3d9d1b5cd69155", [:mix], [], "hexpm", "03a3f6ff16dcbb53e219b99c7af6aab29eb6b88acf80164b4bd76ac18dc890b3"}, From d5acfb5abc7e0151a8003e520175c127f2ca2548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:56:09 +0100 Subject: [PATCH 09/19] Implement search queries --- lib/swapi_web/graphql/queries/film_queries.ex | 8 ++++++++ lib/swapi_web/graphql/queries/person_queries.ex | 8 ++++++++ lib/swapi_web/graphql/queries/planet_queries.ex | 8 ++++++++ lib/swapi_web/graphql/queries/species_queries.ex | 8 ++++++++ lib/swapi_web/graphql/queries/starship_queries.ex | 8 ++++++++ lib/swapi_web/graphql/queries/vehicle_queries.ex | 8 ++++++++ lib/swapi_web/graphql/resolvers/film_resolver.ex | 5 +++++ lib/swapi_web/graphql/resolvers/person_resolver.ex | 5 +++++ lib/swapi_web/graphql/resolvers/planet_resolver.ex | 5 +++++ lib/swapi_web/graphql/resolvers/species_resolver.ex | 5 +++++ lib/swapi_web/graphql/resolvers/starship_resolver.ex | 5 +++++ lib/swapi_web/graphql/resolvers/vehicle_resolver.ex | 5 +++++ 12 files changed, 78 insertions(+) diff --git a/lib/swapi_web/graphql/queries/film_queries.ex b/lib/swapi_web/graphql/queries/film_queries.ex index b849286..d97fe86 100644 --- a/lib/swapi_web/graphql/queries/film_queries.ex +++ b/lib/swapi_web/graphql/queries/film_queries.ex @@ -14,5 +14,13 @@ defmodule SWAPIWeb.GraphQL.Queries.FilmQueries do resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.one/2) end + + @desc "Search films by title." + field :search_films, list_of(:film) do + @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." + arg(:search_terms, non_null(list_of(non_null(:string)))) + + resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.search/2) + end end end diff --git a/lib/swapi_web/graphql/queries/person_queries.ex b/lib/swapi_web/graphql/queries/person_queries.ex index e852222..0f5cc01 100644 --- a/lib/swapi_web/graphql/queries/person_queries.ex +++ b/lib/swapi_web/graphql/queries/person_queries.ex @@ -14,5 +14,13 @@ defmodule SWAPIWeb.GraphQL.Queries.PersonQueries do resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.one/2) end + + @desc "Search people by name." + field :search_people, list_of(:person) do + @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." + arg(:search_terms, non_null(list_of(non_null(:string)))) + + resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.search/2) + end end end diff --git a/lib/swapi_web/graphql/queries/planet_queries.ex b/lib/swapi_web/graphql/queries/planet_queries.ex index b76c7ff..e6b3e12 100644 --- a/lib/swapi_web/graphql/queries/planet_queries.ex +++ b/lib/swapi_web/graphql/queries/planet_queries.ex @@ -14,5 +14,13 @@ defmodule SWAPIWeb.GraphQL.Queries.PlanetQueries do resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.one/2) end + + @desc "Search planets by name." + field :search_planets, list_of(:planet) do + @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." + arg(:search_terms, non_null(list_of(non_null(:string)))) + + resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.search/2) + end end end diff --git a/lib/swapi_web/graphql/queries/species_queries.ex b/lib/swapi_web/graphql/queries/species_queries.ex index 47f5353..a8428ca 100644 --- a/lib/swapi_web/graphql/queries/species_queries.ex +++ b/lib/swapi_web/graphql/queries/species_queries.ex @@ -14,5 +14,13 @@ defmodule SWAPIWeb.GraphQL.Queries.SpeciesQueries do resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.one/2) end + + @desc "Search species by name." + field :search_species, list_of(:species) do + @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." + arg(:search_terms, non_null(list_of(non_null(:string)))) + + resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.search/2) + end end end diff --git a/lib/swapi_web/graphql/queries/starship_queries.ex b/lib/swapi_web/graphql/queries/starship_queries.ex index 8c8a899..4f8b1ef 100644 --- a/lib/swapi_web/graphql/queries/starship_queries.ex +++ b/lib/swapi_web/graphql/queries/starship_queries.ex @@ -14,5 +14,13 @@ defmodule SWAPIWeb.GraphQL.Queries.StarshipQueries do resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.one/2) end + + @desc "Search starships by name or model." + field :search_starships, list_of(:starship) do + @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." + arg(:search_terms, non_null(list_of(non_null(:string)))) + + resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.search/2) + end end end diff --git a/lib/swapi_web/graphql/queries/vehicle_queries.ex b/lib/swapi_web/graphql/queries/vehicle_queries.ex index 275f2a3..2d7ec17 100644 --- a/lib/swapi_web/graphql/queries/vehicle_queries.ex +++ b/lib/swapi_web/graphql/queries/vehicle_queries.ex @@ -14,5 +14,13 @@ defmodule SWAPIWeb.GraphQL.Queries.VehicleQueries do resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.one/2) end + + @desc "Search vehicles by name or model." + field :search_vehicles, list_of(:vehicle) do + @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." + arg(:search_terms, non_null(list_of(non_null(:string)))) + + resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.search/2) + end end end diff --git a/lib/swapi_web/graphql/resolvers/film_resolver.ex b/lib/swapi_web/graphql/resolvers/film_resolver.ex index 220decd..d3ce5ae 100644 --- a/lib/swapi_web/graphql/resolvers/film_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/film_resolver.ex @@ -17,4 +17,9 @@ defmodule SWAPIWeb.GraphQL.Resolvers.FilmResolver do {:error, :not_found} -> {:error, "Film not found"} end end + + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Film.t())} | {:error, any} + def search(%{search_terms: search_terms}, _info) do + {:ok, Films.search_films(search_terms)} + end end diff --git a/lib/swapi_web/graphql/resolvers/person_resolver.ex b/lib/swapi_web/graphql/resolvers/person_resolver.ex index 4f55383..51e215d 100644 --- a/lib/swapi_web/graphql/resolvers/person_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/person_resolver.ex @@ -17,4 +17,9 @@ defmodule SWAPIWeb.GraphQL.Resolvers.PersonResolver do {:error, :not_found} -> {:error, "Person not found"} end end + + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Person.t())} | {:error, any} + def search(%{search_terms: search_terms}, _info) do + {:ok, People.search_people(search_terms)} + end end diff --git a/lib/swapi_web/graphql/resolvers/planet_resolver.ex b/lib/swapi_web/graphql/resolvers/planet_resolver.ex index bba305a..d4c2572 100644 --- a/lib/swapi_web/graphql/resolvers/planet_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/planet_resolver.ex @@ -17,4 +17,9 @@ defmodule SWAPIWeb.GraphQL.Resolvers.PlanetResolver do {:error, :not_found} -> {:error, "Planet not found"} end end + + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Planet.t())} | {:error, any} + def search(%{search_terms: search_terms}, _info) do + {:ok, Planets.search_planets(search_terms)} + end end diff --git a/lib/swapi_web/graphql/resolvers/species_resolver.ex b/lib/swapi_web/graphql/resolvers/species_resolver.ex index 086da75..da2f4d5 100644 --- a/lib/swapi_web/graphql/resolvers/species_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/species_resolver.ex @@ -17,4 +17,9 @@ defmodule SWAPIWeb.GraphQL.Resolvers.SpeciesResolver do {:error, :not_found} -> {:error, "Species not found"} end end + + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Species.t())} | {:error, any} + def search(%{search_terms: search_terms}, _info) do + {:ok, Species.search_species(search_terms)} + end end diff --git a/lib/swapi_web/graphql/resolvers/starship_resolver.ex b/lib/swapi_web/graphql/resolvers/starship_resolver.ex index f6a23a2..0edccc3 100644 --- a/lib/swapi_web/graphql/resolvers/starship_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/starship_resolver.ex @@ -17,4 +17,9 @@ defmodule SWAPIWeb.GraphQL.Resolvers.StarshipResolver do {:error, :not_found} -> {:error, "Starship not found"} end end + + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Starship.t())} | {:error, any} + def search(%{search_terms: search_terms}, _info) do + {:ok, Starships.search_starships(search_terms)} + end end diff --git a/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex b/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex index 3c3daf5..ec59a0d 100644 --- a/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex @@ -17,4 +17,9 @@ defmodule SWAPIWeb.GraphQL.Resolvers.VehicleResolver do {:error, :not_found} -> {:error, "Vehicle not found"} end end + + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Vehicle.t())} | {:error, any} + def search(%{search_terms: search_terms}, _info) do + {:ok, Vehicles.search_vehicles(search_terms)} + end end From d0077753be1d3b125172bcf4b989ab1a36f129d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 11:31:07 +0100 Subject: [PATCH 10/19] Move Dataloader to own module --- lib/swapi.ex | 4 ---- lib/swapi/dataloader.ex | 5 +++++ lib/swapi_web/graphql/schema.ex | 2 +- lib/swapi_web/graphql/types/film.ex | 10 ++++----- lib/swapi_web/graphql/types/person.ex | 10 ++++----- lib/swapi_web/graphql/types/planet.ex | 6 +++--- lib/swapi_web/graphql/types/species.ex | 6 +++--- lib/swapi_web/graphql/types/starship.ex | 28 ++++++++++++------------- lib/swapi_web/graphql/types/vehicle.ex | 28 ++++++++++++------------- 9 files changed, 50 insertions(+), 49 deletions(-) create mode 100644 lib/swapi/dataloader.ex diff --git a/lib/swapi.ex b/lib/swapi.ex index 5f99e17..0d29aba 100644 --- a/lib/swapi.ex +++ b/lib/swapi.ex @@ -6,8 +6,4 @@ defmodule SWAPI do Contexts are also responsible for managing your data, regardless if it comes from the database, an external API or others. """ - - def data(), do: Dataloader.Ecto.new(SWAPI.Repo, query: &query/2) - - def query(queryable, _params), do: queryable end diff --git a/lib/swapi/dataloader.ex b/lib/swapi/dataloader.ex new file mode 100644 index 0000000..27e525a --- /dev/null +++ b/lib/swapi/dataloader.ex @@ -0,0 +1,5 @@ +defmodule SWAPI.Dataloader do + def data(), do: Dataloader.Ecto.new(SWAPI.Repo, query: &query/2) + + def query(queryable, _params), do: queryable +end diff --git a/lib/swapi_web/graphql/schema.ex b/lib/swapi_web/graphql/schema.ex index a88b7e9..7db9151 100644 --- a/lib/swapi_web/graphql/schema.ex +++ b/lib/swapi_web/graphql/schema.ex @@ -11,7 +11,7 @@ defmodule SWAPIWeb.GraphQL.Schema do def context(ctx) do loader = Dataloader.new() - |> Dataloader.add_source(SWAPI, SWAPI.data()) + |> Dataloader.add_source(SWAPI.Dataloader, SWAPI.Dataloader.data()) Map.put(ctx, :loader, loader) end diff --git a/lib/swapi_web/graphql/types/film.ex b/lib/swapi_web/graphql/types/film.ex index 6b28825..2dcfbc5 100644 --- a/lib/swapi_web/graphql/types/film.ex +++ b/lib/swapi_web/graphql/types/film.ex @@ -28,27 +28,27 @@ defmodule SWAPIWeb.GraphQL.Types.Film do @desc "A list of species that are in this film." field :species, list_of(:species) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of starships that are in this film." field :starships, list_of(:starship) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of vehicles that are in this film." field :vehicles, list_of(:vehicle) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of people that are in this film." field :characters, list_of(:person) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of planets that are in this film." field :planets, list_of(:planet) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "The time that this resource was created." diff --git a/lib/swapi_web/graphql/types/person.ex b/lib/swapi_web/graphql/types/person.ex index ec7e5c3..0139271 100644 --- a/lib/swapi_web/graphql/types/person.ex +++ b/lib/swapi_web/graphql/types/person.ex @@ -34,27 +34,27 @@ defmodule SWAPIWeb.GraphQL.Types.Person do @desc "The planet that this person was born on or inhabits." field :homeworld, :planet do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of films that this person has been in." field :films, list_of(:film) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of species that this person belongs to." field :species, list_of(:species) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of starships that this person has piloted." field :starships, list_of(:starship) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of vehicles that this person has piloted." field :vehicles, list_of(:vehicle) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "The time that this resource was created." diff --git a/lib/swapi_web/graphql/types/planet.ex b/lib/swapi_web/graphql/types/planet.ex index 7f2da4d..534dfea 100644 --- a/lib/swapi_web/graphql/types/planet.ex +++ b/lib/swapi_web/graphql/types/planet.ex @@ -37,17 +37,17 @@ defmodule SWAPIWeb.GraphQL.Types.Planet do @desc "A list of people that live on this planet." field :residents, list_of(:person) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of species that live on this planet." field :species, list_of(:species) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of films that this planet has appeared in." field :films, list_of(:film) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "The time that this resource was created." diff --git a/lib/swapi_web/graphql/types/species.ex b/lib/swapi_web/graphql/types/species.ex index 83ddfa9..785dcb5 100644 --- a/lib/swapi_web/graphql/types/species.ex +++ b/lib/swapi_web/graphql/types/species.ex @@ -37,17 +37,17 @@ defmodule SWAPIWeb.GraphQL.Types.Species do @desc "The planet that this species originates from." field :homeworld, :planet do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of people that are a part of this species." field :people, list_of(:person) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of films that this species has appeared in." field :films, list_of(:film) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "The time that this resource was created." diff --git a/lib/swapi_web/graphql/types/starship.ex b/lib/swapi_web/graphql/types/starship.ex index 7f3a743..e7f8996 100644 --- a/lib/swapi_web/graphql/types/starship.ex +++ b/lib/swapi_web/graphql/types/starship.ex @@ -11,12 +11,12 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do @desc "The name of this starship. The common name, such as \"Death Star\"." field :name, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The model or official name of this starship. Such as \"T-65 X-wing\" or \"DS-1 Orbital Battle Station\"." field :model, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The class of this starship, such as \"Starfighter\" or \"Deep Space Mobile Battlestation\"" @@ -24,32 +24,32 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do @desc "The manufacturer of this starship. Comma separated if more than one." field :manufacturer, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The cost of this starship new, in galactic credits." field :cost_in_credits, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The length of this starship in meters." field :length, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The number of personnel needed to run or pilot this starship." field :crew, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The number of non-essential people this starship can transport." field :passengers, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The maximum speed of this starship in the atmosphere. \"N/A\" if this starship is incapable of atmospheric flight." field :max_atmosphering_speed, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The class of this starships hyperdrive." @@ -60,32 +60,32 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do @desc "The maximum number of kilograms that this starship can transport." field :cargo_capacity, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The maximum length of time that this starship can provide consumables for its entire crew without having to resupply." field :consumables, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "A list of films that this starship has appeared in." field :films, list_of(:film) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of people that this starship has been piloted by." field :pilots, list_of(:person) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "The time that this resource was created." field :created, :datetime do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The time that this resource was edited." field :edited, :datetime do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end end end diff --git a/lib/swapi_web/graphql/types/vehicle.ex b/lib/swapi_web/graphql/types/vehicle.ex index 8155459..8968edb 100644 --- a/lib/swapi_web/graphql/types/vehicle.ex +++ b/lib/swapi_web/graphql/types/vehicle.ex @@ -11,12 +11,12 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do @desc "The name of this vehicle. The common name, such as \"Sand Crawler\" or \"Speeder bike\"." field :name, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The model or official name of this vehicle. Such as \"All-Terrain Attack Transport\"." field :model, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The class of this vehicle, such as \"Wheeled\" or \"Repulsorcraft\"." @@ -24,62 +24,62 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do @desc "The manufacturer of this vehicle. Comma separated if more than one." field :manufacturer, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The length of this vehicle in meters." field :cost_in_credits, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The cost of this vehicle new, in Galactic Credits." field :length, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The number of personnel needed to run or pilot this vehicle." field :crew, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The number of non-essential people this vehicle can transport." field :passengers, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The maximum speed of this vehicle in the atmosphere." field :max_atmosphering_speed, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The maximum number of kilograms that this vehicle can transport." field :cargo_capacity, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The maximum length of time that this vehicle can provide consumables for its entire crew without having to resupply." field :consumables, :string do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "A list of films that this vehicle has appeared in." field :films, list_of(:film) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "A list of people that this vehicle has been piloted by." field :pilots, list_of(:person) do - resolve(dataloader(SWAPI)) + resolve(dataloader(SWAPI.Dataloader)) end @desc "The time that this resource was created." field :created, :datetime do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @desc "The time that this resource was edited." field :edited, :datetime do - resolve(dataloader(SWAPI, :transport, callback: &transport_field_callback/4)) + resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end end end From 638d70c2bf0a04882c918c46fe642203d81601ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 11:37:20 +0100 Subject: [PATCH 11/19] Fix alias warnings --- lib/swapi_web/graphql/queries/film_queries.ex | 8 +++++--- lib/swapi_web/graphql/queries/person_queries.ex | 8 +++++--- lib/swapi_web/graphql/queries/planet_queries.ex | 8 +++++--- lib/swapi_web/graphql/queries/species_queries.ex | 8 +++++--- lib/swapi_web/graphql/queries/starship_queries.ex | 8 +++++--- lib/swapi_web/graphql/queries/vehicle_queries.ex | 8 +++++--- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/swapi_web/graphql/queries/film_queries.ex b/lib/swapi_web/graphql/queries/film_queries.ex index d97fe86..93ea9d8 100644 --- a/lib/swapi_web/graphql/queries/film_queries.ex +++ b/lib/swapi_web/graphql/queries/film_queries.ex @@ -1,10 +1,12 @@ defmodule SWAPIWeb.GraphQL.Queries.FilmQueries do use Absinthe.Schema.Notation + alias SWAPIWeb.GraphQL.Resolvers.FilmResolver + object :film_queries do @desc "Get all films." field :all_films, list_of(:film) do - resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.all/2) + resolve(&FilmResolver.all/2) end @desc "Get a film by ID." @@ -12,7 +14,7 @@ defmodule SWAPIWeb.GraphQL.Queries.FilmQueries do @desc "The ID of the film." arg(:id, non_null(:id)) - resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.one/2) + resolve(&FilmResolver.one/2) end @desc "Search films by title." @@ -20,7 +22,7 @@ defmodule SWAPIWeb.GraphQL.Queries.FilmQueries do @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." arg(:search_terms, non_null(list_of(non_null(:string)))) - resolve(&SWAPIWeb.GraphQL.Resolvers.FilmResolver.search/2) + resolve(&FilmResolver.search/2) end end end diff --git a/lib/swapi_web/graphql/queries/person_queries.ex b/lib/swapi_web/graphql/queries/person_queries.ex index 0f5cc01..b56ceff 100644 --- a/lib/swapi_web/graphql/queries/person_queries.ex +++ b/lib/swapi_web/graphql/queries/person_queries.ex @@ -1,10 +1,12 @@ defmodule SWAPIWeb.GraphQL.Queries.PersonQueries do use Absinthe.Schema.Notation + alias SWAPIWeb.GraphQL.Resolvers.PersonResolver + object :person_queries do @desc "Get all people." field :all_people, list_of(:person) do - resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.all/2) + resolve(&PersonResolver.all/2) end @desc "Get a person by ID." @@ -12,7 +14,7 @@ defmodule SWAPIWeb.GraphQL.Queries.PersonQueries do @desc "The ID of the person." arg(:id, non_null(:id)) - resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.one/2) + resolve(&PersonResolver.one/2) end @desc "Search people by name." @@ -20,7 +22,7 @@ defmodule SWAPIWeb.GraphQL.Queries.PersonQueries do @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." arg(:search_terms, non_null(list_of(non_null(:string)))) - resolve(&SWAPIWeb.GraphQL.Resolvers.PersonResolver.search/2) + resolve(&PersonResolver.search/2) end end end diff --git a/lib/swapi_web/graphql/queries/planet_queries.ex b/lib/swapi_web/graphql/queries/planet_queries.ex index e6b3e12..174862f 100644 --- a/lib/swapi_web/graphql/queries/planet_queries.ex +++ b/lib/swapi_web/graphql/queries/planet_queries.ex @@ -1,10 +1,12 @@ defmodule SWAPIWeb.GraphQL.Queries.PlanetQueries do use Absinthe.Schema.Notation + alias SWAPIWeb.GraphQL.Resolvers.PlanetResolver + object :planet_queries do @desc "Get all planets." field :all_planets, list_of(:planet) do - resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.all/2) + resolve(&PlanetResolver.all/2) end @desc "Get a planet by ID." @@ -12,7 +14,7 @@ defmodule SWAPIWeb.GraphQL.Queries.PlanetQueries do @desc "The ID of the planet." arg(:id, non_null(:id)) - resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.one/2) + resolve(&PlanetResolver.one/2) end @desc "Search planets by name." @@ -20,7 +22,7 @@ defmodule SWAPIWeb.GraphQL.Queries.PlanetQueries do @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." arg(:search_terms, non_null(list_of(non_null(:string)))) - resolve(&SWAPIWeb.GraphQL.Resolvers.PlanetResolver.search/2) + resolve(&PlanetResolver.search/2) end end end diff --git a/lib/swapi_web/graphql/queries/species_queries.ex b/lib/swapi_web/graphql/queries/species_queries.ex index a8428ca..9b17691 100644 --- a/lib/swapi_web/graphql/queries/species_queries.ex +++ b/lib/swapi_web/graphql/queries/species_queries.ex @@ -1,10 +1,12 @@ defmodule SWAPIWeb.GraphQL.Queries.SpeciesQueries do use Absinthe.Schema.Notation + alias SWAPIWeb.GraphQL.Resolvers.SpeciesResolver + object :species_queries do @desc "Get all species." field :all_species, list_of(:species) do - resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.all/2) + resolve(&SpeciesResolver.all/2) end @desc "Get a species by ID." @@ -12,7 +14,7 @@ defmodule SWAPIWeb.GraphQL.Queries.SpeciesQueries do @desc "The ID of the species." arg(:id, non_null(:id)) - resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.one/2) + resolve(&SpeciesResolver.one/2) end @desc "Search species by name." @@ -20,7 +22,7 @@ defmodule SWAPIWeb.GraphQL.Queries.SpeciesQueries do @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." arg(:search_terms, non_null(list_of(non_null(:string)))) - resolve(&SWAPIWeb.GraphQL.Resolvers.SpeciesResolver.search/2) + resolve(&SpeciesResolver.search/2) end end end diff --git a/lib/swapi_web/graphql/queries/starship_queries.ex b/lib/swapi_web/graphql/queries/starship_queries.ex index 4f8b1ef..37bd1bf 100644 --- a/lib/swapi_web/graphql/queries/starship_queries.ex +++ b/lib/swapi_web/graphql/queries/starship_queries.ex @@ -1,10 +1,12 @@ defmodule SWAPIWeb.GraphQL.Queries.StarshipQueries do use Absinthe.Schema.Notation + alias SWAPIWeb.GraphQL.Resolvers.StarshipResolver + object :starship_queries do @desc "Get all starships." field :all_starships, list_of(:starship) do - resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.all/2) + resolve(&StarshipResolver.all/2) end @desc "Get a starship by ID." @@ -12,7 +14,7 @@ defmodule SWAPIWeb.GraphQL.Queries.StarshipQueries do @desc "The ID of the starship." arg(:id, non_null(:id)) - resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.one/2) + resolve(&StarshipResolver.one/2) end @desc "Search starships by name or model." @@ -20,7 +22,7 @@ defmodule SWAPIWeb.GraphQL.Queries.StarshipQueries do @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." arg(:search_terms, non_null(list_of(non_null(:string)))) - resolve(&SWAPIWeb.GraphQL.Resolvers.StarshipResolver.search/2) + resolve(&StarshipResolver.search/2) end end end diff --git a/lib/swapi_web/graphql/queries/vehicle_queries.ex b/lib/swapi_web/graphql/queries/vehicle_queries.ex index 2d7ec17..0e0eff3 100644 --- a/lib/swapi_web/graphql/queries/vehicle_queries.ex +++ b/lib/swapi_web/graphql/queries/vehicle_queries.ex @@ -1,10 +1,12 @@ defmodule SWAPIWeb.GraphQL.Queries.VehicleQueries do use Absinthe.Schema.Notation + alias SWAPIWeb.GraphQL.Resolvers.VehicleResolver + object :vehicle_queries do @desc "Get all vehicles." field :all_vehicles, list_of(:vehicle) do - resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.all/2) + resolve(&VehicleResolver.all/2) end @desc "Get a vehicle by ID." @@ -12,7 +14,7 @@ defmodule SWAPIWeb.GraphQL.Queries.VehicleQueries do @desc "The ID of the vehicle." arg(:id, non_null(:id)) - resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.one/2) + resolve(&VehicleResolver.one/2) end @desc "Search vehicles by name or model." @@ -20,7 +22,7 @@ defmodule SWAPIWeb.GraphQL.Queries.VehicleQueries do @desc "A list of search terms. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched." arg(:search_terms, non_null(list_of(non_null(:string)))) - resolve(&SWAPIWeb.GraphQL.Resolvers.VehicleResolver.search/2) + resolve(&VehicleResolver.search/2) end end end From a073082ae36d78d087fb6a29fbe700a1361c9b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 11:47:31 +0100 Subject: [PATCH 12/19] Fix typespecs --- lib/swapi_web/graphql/resolvers/film_resolver.ex | 1 + lib/swapi_web/graphql/resolvers/person_resolver.ex | 1 + lib/swapi_web/graphql/resolvers/planet_resolver.ex | 1 + lib/swapi_web/graphql/resolvers/species_resolver.ex | 7 ++++--- lib/swapi_web/graphql/resolvers/starship_resolver.ex | 1 + lib/swapi_web/graphql/resolvers/vehicle_resolver.ex | 1 + 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/swapi_web/graphql/resolvers/film_resolver.ex b/lib/swapi_web/graphql/resolvers/film_resolver.ex index d3ce5ae..bd8bf94 100644 --- a/lib/swapi_web/graphql/resolvers/film_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/film_resolver.ex @@ -4,6 +4,7 @@ defmodule SWAPIWeb.GraphQL.Resolvers.FilmResolver do """ alias SWAPI.Films + alias SWAPI.Schemas.Film @spec all(map, map) :: {:ok, list(Film.t())} | {:error, any} def all(_args, _info) do diff --git a/lib/swapi_web/graphql/resolvers/person_resolver.ex b/lib/swapi_web/graphql/resolvers/person_resolver.ex index 51e215d..1dd34f9 100644 --- a/lib/swapi_web/graphql/resolvers/person_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/person_resolver.ex @@ -4,6 +4,7 @@ defmodule SWAPIWeb.GraphQL.Resolvers.PersonResolver do """ alias SWAPI.People + alias SWAPI.Schemas.Person @spec all(map, map) :: {:ok, list(Person.t())} | {:error, any} def all(_args, _info) do diff --git a/lib/swapi_web/graphql/resolvers/planet_resolver.ex b/lib/swapi_web/graphql/resolvers/planet_resolver.ex index d4c2572..72b463c 100644 --- a/lib/swapi_web/graphql/resolvers/planet_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/planet_resolver.ex @@ -4,6 +4,7 @@ defmodule SWAPIWeb.GraphQL.Resolvers.PlanetResolver do """ alias SWAPI.Planets + alias SWAPI.Schemas.Planet @spec all(map, map) :: {:ok, list(Planet.t())} | {:error, any} def all(_args, _info) do diff --git a/lib/swapi_web/graphql/resolvers/species_resolver.ex b/lib/swapi_web/graphql/resolvers/species_resolver.ex index da2f4d5..78f1075 100644 --- a/lib/swapi_web/graphql/resolvers/species_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/species_resolver.ex @@ -4,13 +4,14 @@ defmodule SWAPIWeb.GraphQL.Resolvers.SpeciesResolver do """ alias SWAPI.Species + alias SWAPI.Schemas.Species, as: SpeciesSchema - @spec all(map, map) :: {:ok, list(Species.t())} | {:error, any} + @spec all(map, map) :: {:ok, list(SpeciesSchema.t())} | {:error, any} def all(_args, _info) do {:ok, Species.list_species()} end - @spec one(map, Absinthe.Resolution.t()) :: {:ok, Species.t()} | {:error, any} + @spec one(map, Absinthe.Resolution.t()) :: {:ok, SpeciesSchema.t()} | {:error, any} def one(%{id: id}, _info) do case Species.get_species(id) do {:ok, species} -> {:ok, species} @@ -18,7 +19,7 @@ defmodule SWAPIWeb.GraphQL.Resolvers.SpeciesResolver do end end - @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(Species.t())} | {:error, any} + @spec search(map, Absinthe.Blueprint.t()) :: {:ok, list(SpeciesSchema.t())} | {:error, any} def search(%{search_terms: search_terms}, _info) do {:ok, Species.search_species(search_terms)} end diff --git a/lib/swapi_web/graphql/resolvers/starship_resolver.ex b/lib/swapi_web/graphql/resolvers/starship_resolver.ex index 0edccc3..f44c33d 100644 --- a/lib/swapi_web/graphql/resolvers/starship_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/starship_resolver.ex @@ -3,6 +3,7 @@ defmodule SWAPIWeb.GraphQL.Resolvers.StarshipResolver do Starship resolver. """ + alias SWAPI.Schemas.Starship alias SWAPI.Starships @spec all(map, map) :: {:ok, list(Starship.t())} | {:error, any} diff --git a/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex b/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex index ec59a0d..a5d8fb1 100644 --- a/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/vehicle_resolver.ex @@ -3,6 +3,7 @@ defmodule SWAPIWeb.GraphQL.Resolvers.VehicleResolver do Vehicle resolver. """ + alias SWAPI.Schemas.Vehicle alias SWAPI.Vehicles @spec all(map, map) :: {:ok, list(Vehicle.t())} | {:error, any} From e875712485b121bd6742c94186c5fab9f4b0de2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:59:51 +0100 Subject: [PATCH 13/19] Add GraphQL resolver tests --- .../graphql/resolvers/film_resolver_test.exs | 43 ++++++++++++++++++ .../resolvers/person_resolver_test.exs | 43 ++++++++++++++++++ .../resolvers/planet_resolver_test.exs | 44 +++++++++++++++++++ .../resolvers/species_resolver_test.exs | 43 ++++++++++++++++++ .../resolvers/starship_resolver_test.exs | 44 +++++++++++++++++++ .../resolvers/vehicle_resolver_test.exs | 43 ++++++++++++++++++ 6 files changed, 260 insertions(+) create mode 100644 test/swapi_web/graphql/resolvers/film_resolver_test.exs create mode 100644 test/swapi_web/graphql/resolvers/person_resolver_test.exs create mode 100644 test/swapi_web/graphql/resolvers/planet_resolver_test.exs create mode 100644 test/swapi_web/graphql/resolvers/species_resolver_test.exs create mode 100644 test/swapi_web/graphql/resolvers/starship_resolver_test.exs create mode 100644 test/swapi_web/graphql/resolvers/vehicle_resolver_test.exs diff --git a/test/swapi_web/graphql/resolvers/film_resolver_test.exs b/test/swapi_web/graphql/resolvers/film_resolver_test.exs new file mode 100644 index 0000000..46c1912 --- /dev/null +++ b/test/swapi_web/graphql/resolvers/film_resolver_test.exs @@ -0,0 +1,43 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.FilmResolverTest do + use SWAPI.DataCase + + alias SWAPIWeb.GraphQL.Resolvers.FilmResolver + import SWAPI.FilmsFixtures + + describe "all/2" do + test "returns all films" do + %{id: film_id} = film_fixture() + assert {:ok, [%{id: ^film_id}]} = FilmResolver.all(%{}, %{}) + end + + test "returns empty list when no films exist" do + assert {:ok, []} = FilmResolver.all(%{}, %{}) + end + end + + describe "one/2" do + test "returns film when it exists" do + %{id: film_id} = film_fixture() + assert {:ok, %{id: ^film_id}} = FilmResolver.one(%{id: film_id}, %{}) + end + + test "returns error when film doesn't exist" do + assert {:error, "Film not found"} = FilmResolver.one(%{id: 0}, %{}) + end + end + + describe "search/2" do + test "returns matching films" do + %{id: film_id} = film_fixture(%{title: "A New Hope"}) + film_fixture(%{title: "Empire Strikes Back"}) + + assert {:ok, [%{id: ^film_id}]} = FilmResolver.search(%{search_terms: ["Hope"]}, %{}) + end + + test "returns empty list when no matches found" do + film_fixture(%{title: "A New Hope"}) + + assert {:ok, []} = FilmResolver.search(%{search_terms: ["Non-existent"]}, %{}) + end + end +end diff --git a/test/swapi_web/graphql/resolvers/person_resolver_test.exs b/test/swapi_web/graphql/resolvers/person_resolver_test.exs new file mode 100644 index 0000000..03222d7 --- /dev/null +++ b/test/swapi_web/graphql/resolvers/person_resolver_test.exs @@ -0,0 +1,43 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.PersonResolverTest do + use SWAPI.DataCase + + alias SWAPIWeb.GraphQL.Resolvers.PersonResolver + import SWAPI.PeopleFixtures + + describe "all/2" do + test "returns all people" do + %{id: person_id} = person_fixture() + assert {:ok, [%{id: ^person_id}]} = PersonResolver.all(%{}, %{}) + end + + test "returns empty list when no people exist" do + assert {:ok, []} = PersonResolver.all(%{}, %{}) + end + end + + describe "one/2" do + test "returns person when they exist" do + %{id: person_id} = person_fixture() + assert {:ok, %{id: ^person_id}} = PersonResolver.one(%{id: person_id}, %{}) + end + + test "returns error when person doesn't exist" do + assert {:error, "Person not found"} = PersonResolver.one(%{id: 0}, %{}) + end + end + + describe "search/2" do + test "returns matching people" do + %{id: person_id} = person_fixture(%{name: "Luke Skywalker"}) + person_fixture(%{name: "Leia Organa"}) + + assert {:ok, [%{id: ^person_id}]} = PersonResolver.search(%{search_terms: ["Luke"]}, %{}) + end + + test "returns empty list when no matches found" do + person_fixture(%{name: "Luke Skywalker"}) + + assert {:ok, []} = PersonResolver.search(%{search_terms: ["Non-existent"]}, %{}) + end + end +end diff --git a/test/swapi_web/graphql/resolvers/planet_resolver_test.exs b/test/swapi_web/graphql/resolvers/planet_resolver_test.exs new file mode 100644 index 0000000..51491d2 --- /dev/null +++ b/test/swapi_web/graphql/resolvers/planet_resolver_test.exs @@ -0,0 +1,44 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.PlanetResolverTest do + use SWAPI.DataCase + + alias SWAPIWeb.GraphQL.Resolvers.PlanetResolver + import SWAPI.PlanetsFixtures + + describe "all/2" do + test "returns all planets" do + %{id: planet_id} = planet_fixture() + assert {:ok, [%{id: ^planet_id}]} = PlanetResolver.all(%{}, %{}) + end + + test "returns empty list when no planets exist" do + assert {:ok, []} = PlanetResolver.all(%{}, %{}) + end + end + + describe "one/2" do + test "returns planet when it exists" do + %{id: planet_id} = planet_fixture() + assert {:ok, %{id: ^planet_id}} = PlanetResolver.one(%{id: planet_id}, %{}) + end + + test "returns error when planet doesn't exist" do + assert {:error, "Planet not found"} = PlanetResolver.one(%{id: 0}, %{}) + end + end + + describe "search/2" do + test "returns matching planets" do + %{id: planet_id} = planet_fixture(%{name: "Tatooine"}) + planet_fixture(%{name: "Alderaan"}) + + assert {:ok, [%{id: ^planet_id}]} = + PlanetResolver.search(%{search_terms: ["Tatooine"]}, %{}) + end + + test "returns empty list when no matches found" do + planet_fixture(%{name: "Tatooine"}) + + assert {:ok, []} = PlanetResolver.search(%{search_terms: ["Non-existent"]}, %{}) + end + end +end diff --git a/test/swapi_web/graphql/resolvers/species_resolver_test.exs b/test/swapi_web/graphql/resolvers/species_resolver_test.exs new file mode 100644 index 0000000..aeb5209 --- /dev/null +++ b/test/swapi_web/graphql/resolvers/species_resolver_test.exs @@ -0,0 +1,43 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.SpeciesResolverTest do + use SWAPI.DataCase + + alias SWAPIWeb.GraphQL.Resolvers.SpeciesResolver + import SWAPI.SpeciesFixtures + + describe "all/2" do + test "returns all species" do + %{id: species_id} = species_fixture() + assert {:ok, [%{id: ^species_id}]} = SpeciesResolver.all(%{}, %{}) + end + + test "returns empty list when no species exist" do + assert {:ok, []} = SpeciesResolver.all(%{}, %{}) + end + end + + describe "one/2" do + test "returns species when it exists" do + %{id: species_id} = species_fixture() + assert {:ok, %{id: ^species_id}} = SpeciesResolver.one(%{id: species_id}, %{}) + end + + test "returns error when species doesn't exist" do + assert {:error, "Species not found"} = SpeciesResolver.one(%{id: 0}, %{}) + end + end + + describe "search/2" do + test "returns matching species" do + %{id: species_id} = species_fixture(%{name: "Human"}) + species_fixture(%{name: "Wookiee"}) + + assert {:ok, [%{id: ^species_id}]} = SpeciesResolver.search(%{search_terms: ["Human"]}, %{}) + end + + test "returns empty list when no matches found" do + species_fixture(%{name: "Human"}) + + assert {:ok, []} = SpeciesResolver.search(%{search_terms: ["Non-existent"]}, %{}) + end + end +end diff --git a/test/swapi_web/graphql/resolvers/starship_resolver_test.exs b/test/swapi_web/graphql/resolvers/starship_resolver_test.exs new file mode 100644 index 0000000..0a77b1c --- /dev/null +++ b/test/swapi_web/graphql/resolvers/starship_resolver_test.exs @@ -0,0 +1,44 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.StarshipResolverTest do + use SWAPI.DataCase + + alias SWAPIWeb.GraphQL.Resolvers.StarshipResolver + import SWAPI.StarshipsFixtures + + describe "all/2" do + test "returns all starships" do + %{id: starship_id} = starship_fixture() + assert {:ok, [%{id: ^starship_id}]} = StarshipResolver.all(%{}, %{}) + end + + test "returns empty list when no starships exist" do + assert {:ok, []} = StarshipResolver.all(%{}, %{}) + end + end + + describe "one/2" do + test "returns starship when it exists" do + %{id: starship_id} = starship_fixture() + assert {:ok, %{id: ^starship_id}} = StarshipResolver.one(%{id: starship_id}, %{}) + end + + test "returns error when starship doesn't exist" do + assert {:error, "Starship not found"} = StarshipResolver.one(%{id: 0}, %{}) + end + end + + describe "search/2" do + test "returns matching starships" do + %{id: starship_id} = starship_fixture(%{transport: %{name: "X-wing"}}) + starship_fixture(%{transport: %{name: "TIE Fighter"}}) + + assert {:ok, [%{id: ^starship_id}]} = + StarshipResolver.search(%{search_terms: ["X-wing"]}, %{}) + end + + test "returns empty list when no matches found" do + starship_fixture(%{transport: %{name: "X-wing"}}) + + assert {:ok, []} = StarshipResolver.search(%{search_terms: ["Non-existent"]}, %{}) + end + end +end diff --git a/test/swapi_web/graphql/resolvers/vehicle_resolver_test.exs b/test/swapi_web/graphql/resolvers/vehicle_resolver_test.exs new file mode 100644 index 0000000..18a7122 --- /dev/null +++ b/test/swapi_web/graphql/resolvers/vehicle_resolver_test.exs @@ -0,0 +1,43 @@ +defmodule SWAPIWeb.GraphQL.Resolvers.VehicleResolverTest do + use SWAPI.DataCase + + alias SWAPIWeb.GraphQL.Resolvers.VehicleResolver + import SWAPI.VehiclesFixtures + + describe "all/2" do + test "returns all vehicles" do + %{id: vehicle_id} = vehicle_fixture() + assert {:ok, [%{id: ^vehicle_id}]} = VehicleResolver.all(%{}, %{}) + end + + test "returns empty list when no vehicles exist" do + assert {:ok, []} = VehicleResolver.all(%{}, %{}) + end + end + + describe "one/2" do + test "returns vehicle when it exists" do + %{id: vehicle_id} = vehicle_fixture() + assert {:ok, %{id: ^vehicle_id}} = VehicleResolver.one(%{id: vehicle_id}, %{}) + end + + test "returns error when vehicle doesn't exist" do + assert {:error, "Vehicle not found"} = VehicleResolver.one(%{id: 0}, %{}) + end + end + + describe "search/2" do + test "returns matching vehicles" do + %{id: vehicle_id} = vehicle_fixture(%{transport: %{name: "AT-AT"}}) + vehicle_fixture(%{name: "Snowspeeder"}) + + assert {:ok, [%{id: ^vehicle_id}]} = VehicleResolver.search(%{search_terms: ["AT-AT"]}, %{}) + end + + test "returns empty list when no matches found" do + vehicle_fixture(%{transport: %{name: "AT-AT"}}) + + assert {:ok, []} = VehicleResolver.search(%{search_terms: ["Non-existent"]}, %{}) + end + end +end From 3b74200f2f2ffe84a8ed9f7bc054c2e56c683cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:38:04 +0100 Subject: [PATCH 14/19] Add tests for GraphQL queries --- .../graphql/queries/film_queries_test.exs | 182 ++++++++++++++++++ .../graphql/queries/person_queries_test.exs | 182 ++++++++++++++++++ .../graphql/queries/planet_queries_test.exs | 161 ++++++++++++++++ .../graphql/queries/species_queries_test.exs | 168 ++++++++++++++++ .../graphql/queries/starship_queries_test.exs | 161 ++++++++++++++++ .../graphql/queries/vehicle_queries_test.exs | 161 ++++++++++++++++ 6 files changed, 1015 insertions(+) create mode 100644 test/swapi_web/graphql/queries/film_queries_test.exs create mode 100644 test/swapi_web/graphql/queries/person_queries_test.exs create mode 100644 test/swapi_web/graphql/queries/planet_queries_test.exs create mode 100644 test/swapi_web/graphql/queries/species_queries_test.exs create mode 100644 test/swapi_web/graphql/queries/starship_queries_test.exs create mode 100644 test/swapi_web/graphql/queries/vehicle_queries_test.exs diff --git a/test/swapi_web/graphql/queries/film_queries_test.exs b/test/swapi_web/graphql/queries/film_queries_test.exs new file mode 100644 index 0000000..90d9fd9 --- /dev/null +++ b/test/swapi_web/graphql/queries/film_queries_test.exs @@ -0,0 +1,182 @@ +defmodule SWAPIWeb.GraphQL.QueriesTests do + use SWAPIWeb.ConnCase + + import Ecto.Changeset + + import SWAPI.FilmsFixtures + import SWAPI.PeopleFixtures + import SWAPI.PlanetsFixtures + import SWAPI.SpeciesFixtures + import SWAPI.StarshipsFixtures + import SWAPI.VehiclesFixtures + + alias SWAPI.Repo + alias SWAPI.Schemas.Film + + setup do + film = + film_fixture() + |> Film.changeset(%{title: "A New Hope"}) + |> put_assoc(:species, [species_fixture()]) + |> put_assoc(:starships, [starship_fixture()]) + |> put_assoc(:vehicles, [vehicle_fixture()]) + |> put_assoc(:characters, [person_fixture()]) + |> put_assoc(:planets, [planet_fixture()]) + |> Repo.update!() + + {:ok, %{film: film}} + end + + describe "allFilms" do + test "returns all films", %{conn: conn, film: film1} do + film2 = film_fixture() + + query = """ + query { + allFilms { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "allFilms" => [ + %{"id" => film1_id}, + %{"id" => film2_id} + ] + } + } = json_response(conn, 200) + + assert ^film1_id = "#{film1.id}" + assert ^film2_id = "#{film2.id}" + end + end + + describe "searchFilms" do + test "returns matching films", %{conn: conn, film: film} do + film_fixture(%{title: "Empire Strikes Back"}) + + query = """ + query { + searchFilms(searchTerms: ["Hope"]) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "searchFilms" => [ + %{"id" => film_id} + ] + } + } = json_response(conn, 200) + + assert ^film_id = "#{film.id}" + end + end + + describe "film" do + test "returns film when it exists", %{conn: conn, film: film} do + query = """ + query { + film(id: #{film.id}) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "film" => %{"id" => film_id} + } + } = json_response(conn, 200) + + assert ^film_id = "#{film.id}" + end + + test "loads nested fields", %{conn: conn, film: film} do + query = """ + query { + film(id: #{film.id}) { + id + species { + id + } + starships { + id + } + vehicles { + id + } + characters { + id + } + planets { + id + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "film" => %{ + "id" => film_id, + "species" => [%{"id" => species_id}], + "starships" => [%{"id" => starship_id}], + "vehicles" => [%{"id" => vehicle_id}], + "characters" => [%{"id" => person_id}], + "planets" => [%{"id" => planet_id}] + } + } + } = json_response(conn, 200) + + assert ^film_id = "#{film.id}" + assert ^species_id = "#{List.first(film.species).id}" + assert ^starship_id = "#{List.first(film.starships).id}" + assert ^vehicle_id = "#{List.first(film.vehicles).id}" + assert ^person_id = "#{List.first(film.characters).id}" + assert ^planet_id = "#{List.first(film.planets).id}" + end + + test "handles recursive nesting", %{conn: conn, film: film} do + query = """ + query { + film(id: #{film.id}) { + id + characters { + id + films { + id + } + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "film" => %{ + "id" => film_id, + "characters" => [%{"id" => person_id, "films" => [%{"id" => film_id}]}] + } + } + } = json_response(conn, 200) + + assert ^film_id = "#{film.id}" + assert ^person_id = "#{List.first(film.characters).id}" + end + end +end diff --git a/test/swapi_web/graphql/queries/person_queries_test.exs b/test/swapi_web/graphql/queries/person_queries_test.exs new file mode 100644 index 0000000..673f3aa --- /dev/null +++ b/test/swapi_web/graphql/queries/person_queries_test.exs @@ -0,0 +1,182 @@ +defmodule SWAPIWeb.GraphQL.PersonQueriesTest do + use SWAPIWeb.ConnCase + + import Ecto.Changeset + + import SWAPI.FilmsFixtures + import SWAPI.PeopleFixtures + import SWAPI.PlanetsFixtures + import SWAPI.SpeciesFixtures + import SWAPI.StarshipsFixtures + import SWAPI.VehiclesFixtures + + alias SWAPI.Repo + alias SWAPI.Schemas.Person + + setup do + person = + person_fixture() + |> Person.changeset(%{name: "Luke Skywalker"}) + |> put_assoc(:homeworld, planet_fixture()) + |> put_assoc(:films, [film_fixture()]) + |> put_assoc(:species, [species_fixture()]) + |> put_assoc(:starships, [starship_fixture()]) + |> put_assoc(:vehicles, [vehicle_fixture()]) + |> Repo.update!() + + {:ok, %{person: person}} + end + + describe "allPeople" do + test "returns all people", %{conn: conn, person: person1} do + person2 = person_fixture() + + query = """ + query { + allPeople { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "allPeople" => [ + %{"id" => person1_id}, + %{"id" => person2_id} + ] + } + } = json_response(conn, 200) + + assert ^person1_id = "#{person1.id}" + assert ^person2_id = "#{person2.id}" + end + end + + describe "searchPeople" do + test "returns matching people", %{conn: conn, person: person} do + person_fixture(%{name: "Han Solo"}) + + query = """ + query { + searchPeople(searchTerms: ["Luke"]) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "searchPeople" => [ + %{"id" => person_id} + ] + } + } = json_response(conn, 200) + + assert ^person_id = "#{person.id}" + end + end + + describe "person" do + test "returns person when it exists", %{conn: conn, person: person} do + query = """ + query { + person(id: #{person.id}) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "person" => %{"id" => person_id} + } + } = json_response(conn, 200) + + assert ^person_id = "#{person.id}" + end + + test "loads nested fields", %{conn: conn, person: person} do + query = """ + query { + person(id: #{person.id}) { + id + homeworld { + id + } + films { + id + } + species { + id + } + starships { + id + } + vehicles { + id + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "person" => %{ + "id" => person_id, + "homeworld" => %{"id" => homeworld_id}, + "films" => [%{"id" => film_id}], + "species" => [%{"id" => species_id}], + "starships" => [%{"id" => starship_id}], + "vehicles" => [%{"id" => vehicle_id}] + } + } + } = json_response(conn, 200) + + assert ^person_id = "#{person.id}" + assert ^homeworld_id = "#{person.homeworld.id}" + assert ^film_id = "#{List.first(person.films).id}" + assert ^species_id = "#{List.first(person.species).id}" + assert ^starship_id = "#{List.first(person.starships).id}" + assert ^vehicle_id = "#{List.first(person.vehicles).id}" + end + + test "handles recursive nesting", %{conn: conn, person: person} do + query = """ + query { + person(id: #{person.id}) { + id + films { + id + characters { + id + } + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "person" => %{ + "id" => person_id, + "films" => [%{"id" => film_id, "characters" => [%{"id" => person_id}]}] + } + } + } = json_response(conn, 200) + + assert ^person_id = "#{person.id}" + assert ^film_id = "#{List.first(person.films).id}" + end + end +end diff --git a/test/swapi_web/graphql/queries/planet_queries_test.exs b/test/swapi_web/graphql/queries/planet_queries_test.exs new file mode 100644 index 0000000..894649d --- /dev/null +++ b/test/swapi_web/graphql/queries/planet_queries_test.exs @@ -0,0 +1,161 @@ +defmodule SWAPIWeb.GraphQL.PlanetQueriesTest do + use SWAPIWeb.ConnCase + + import Ecto.Changeset + + import SWAPI.FilmsFixtures + import SWAPI.PeopleFixtures + import SWAPI.PlanetsFixtures + + alias SWAPI.Repo + alias SWAPI.Schemas.Planet + + setup do + planet = + planet_fixture() + |> Planet.changeset(%{name: "Tatooine"}) + |> put_assoc(:films, [film_fixture()]) + |> put_assoc(:residents, [person_fixture()]) + |> Repo.update!() + + {:ok, %{planet: planet}} + end + + describe "allPlanets" do + test "returns all planets", %{conn: conn, planet: planet1} do + planet2 = planet_fixture() + + query = """ + query { + allPlanets { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "allPlanets" => [ + %{"id" => planet1_id}, + %{"id" => planet2_id} + ] + } + } = json_response(conn, 200) + + assert ^planet1_id = "#{planet1.id}" + assert ^planet2_id = "#{planet2.id}" + end + end + + describe "searchPlanets" do + test "returns matching planets", %{conn: conn, planet: planet} do + planet_fixture(%{name: "Alderaan"}) + + query = """ + query { + searchPlanets(searchTerms: ["Tatooine"]) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "searchPlanets" => [ + %{"id" => planet_id} + ] + } + } = json_response(conn, 200) + + assert ^planet_id = "#{planet.id}" + end + end + + describe "planet" do + test "returns planet when it exists", %{conn: conn, planet: planet} do + query = """ + query { + planet(id: #{planet.id}) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "planet" => %{"id" => planet_id} + } + } = json_response(conn, 200) + + assert ^planet_id = "#{planet.id}" + end + + test "loads nested fields", %{conn: conn, planet: planet} do + query = """ + query { + planet(id: #{planet.id}) { + id + films { + id + } + residents { + id + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "planet" => %{ + "id" => planet_id, + "films" => [%{"id" => film_id}], + "residents" => [%{"id" => resident_id}] + } + } + } = json_response(conn, 200) + + assert ^planet_id = "#{planet.id}" + assert ^film_id = "#{List.first(planet.films).id}" + assert ^resident_id = "#{List.first(planet.residents).id}" + end + + test "handles recursive nesting", %{conn: conn, planet: planet} do + query = """ + query { + planet(id: #{planet.id}) { + id + residents { + id + homeworld { + id + } + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "planet" => %{ + "id" => planet_id, + "residents" => [%{"id" => resident_id, "homeworld" => %{"id" => planet_id}}] + } + } + } = json_response(conn, 200) + + assert ^planet_id = "#{planet.id}" + assert ^resident_id = "#{List.first(planet.residents).id}" + end + end +end diff --git a/test/swapi_web/graphql/queries/species_queries_test.exs b/test/swapi_web/graphql/queries/species_queries_test.exs new file mode 100644 index 0000000..0c8e264 --- /dev/null +++ b/test/swapi_web/graphql/queries/species_queries_test.exs @@ -0,0 +1,168 @@ +defmodule SWAPIWeb.GraphQL.SpeciesQueriesTest do + use SWAPIWeb.ConnCase + + import Ecto.Changeset + + import SWAPI.FilmsFixtures + import SWAPI.PeopleFixtures + import SWAPI.PlanetsFixtures + import SWAPI.SpeciesFixtures + + alias SWAPI.Repo + alias SWAPI.Schemas.Species + + setup do + species = + species_fixture() + |> Species.changeset(%{name: "Human"}) + |> put_assoc(:homeworld, planet_fixture()) + |> put_assoc(:films, [film_fixture()]) + |> put_assoc(:people, [person_fixture()]) + |> Repo.update!() + + {:ok, %{species: species}} + end + + describe "allSpecies" do + test "returns all species", %{conn: conn, species: species1} do + species2 = species_fixture() + + query = """ + query { + allSpecies { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "allSpecies" => [ + %{"id" => species1_id}, + %{"id" => species2_id} + ] + } + } = json_response(conn, 200) + + assert ^species1_id = "#{species1.id}" + assert ^species2_id = "#{species2.id}" + end + end + + describe "searchSpecies" do + test "returns matching species", %{conn: conn, species: species} do + species_fixture(%{name: "Wookiee"}) + + query = """ + query { + searchSpecies(searchTerms: ["Human"]) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "searchSpecies" => [ + %{"id" => species_id} + ] + } + } = json_response(conn, 200) + + assert ^species_id = "#{species.id}" + end + end + + describe "species" do + test "returns species when it exists", %{conn: conn, species: species} do + query = """ + query { + species(id: #{species.id}) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "species" => %{"id" => species_id} + } + } = json_response(conn, 200) + + assert ^species_id = "#{species.id}" + end + + test "loads nested fields", %{conn: conn, species: species} do + query = """ + query { + species(id: #{species.id}) { + id + homeworld { + id + } + films { + id + } + people { + id + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "species" => %{ + "id" => species_id, + "homeworld" => %{"id" => homeworld_id}, + "films" => [%{"id" => film_id}], + "people" => [%{"id" => person_id}] + } + } + } = json_response(conn, 200) + + assert ^species_id = "#{species.id}" + assert ^homeworld_id = "#{species.homeworld.id}" + assert ^film_id = "#{List.first(species.films).id}" + assert ^person_id = "#{List.first(species.people).id}" + end + + test "handles recursive nesting", %{conn: conn, species: species} do + query = """ + query { + species(id: #{species.id}) { + id + people { + id + species { + id + } + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "species" => %{ + "id" => species_id, + "people" => [%{"id" => person_id, "species" => [%{"id" => species_id}]}] + } + } + } = json_response(conn, 200) + + assert ^species_id = "#{species.id}" + assert ^person_id = "#{List.first(species.people).id}" + end + end +end diff --git a/test/swapi_web/graphql/queries/starship_queries_test.exs b/test/swapi_web/graphql/queries/starship_queries_test.exs new file mode 100644 index 0000000..ab52c12 --- /dev/null +++ b/test/swapi_web/graphql/queries/starship_queries_test.exs @@ -0,0 +1,161 @@ +defmodule SWAPIWeb.GraphQL.StarshipQueriesTest do + use SWAPIWeb.ConnCase + + import Ecto.Changeset + + import SWAPI.FilmsFixtures + import SWAPI.PeopleFixtures + import SWAPI.StarshipsFixtures + + alias SWAPI.Repo + alias SWAPI.Schemas.Starship + + setup do + starship = + starship_fixture() + |> Starship.changeset(%{transport: %{name: "X-wing"}}) + |> put_assoc(:films, [film_fixture()]) + |> put_assoc(:pilots, [person_fixture()]) + |> Repo.update!() + + {:ok, %{starship: starship}} + end + + describe "allStarships" do + test "returns all starships", %{conn: conn, starship: starship1} do + starship2 = starship_fixture() + + query = """ + query { + allStarships { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "allStarships" => [ + %{"id" => starship1_id}, + %{"id" => starship2_id} + ] + } + } = json_response(conn, 200) + + assert ^starship1_id = "#{starship1.id}" + assert ^starship2_id = "#{starship2.id}" + end + end + + describe "searchStarships" do + test "returns matching starships", %{conn: conn, starship: starship} do + starship_fixture(%{name: "Millennium Falcon"}) + + query = """ + query { + searchStarships(searchTerms: ["X-wing"]) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "searchStarships" => [ + %{"id" => starship_id} + ] + } + } = json_response(conn, 200) + + assert ^starship_id = "#{starship.id}" + end + end + + describe "starship" do + test "returns starship when it exists", %{conn: conn, starship: starship} do + query = """ + query { + starship(id: #{starship.id}) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "starship" => %{"id" => starship_id} + } + } = json_response(conn, 200) + + assert ^starship_id = "#{starship.id}" + end + + test "loads nested fields", %{conn: conn, starship: starship} do + query = """ + query { + starship(id: #{starship.id}) { + id + films { + id + } + pilots { + id + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "starship" => %{ + "id" => starship_id, + "films" => [%{"id" => film_id}], + "pilots" => [%{"id" => pilot_id}] + } + } + } = json_response(conn, 200) + + assert ^starship_id = "#{starship.id}" + assert ^film_id = "#{List.first(starship.films).id}" + assert ^pilot_id = "#{List.first(starship.pilots).id}" + end + + test "handles recursive nesting", %{conn: conn, starship: starship} do + query = """ + query { + starship(id: #{starship.id}) { + id + pilots { + id + starships { + id + } + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "starship" => %{ + "id" => starship_id, + "pilots" => [%{"id" => pilot_id, "starships" => [%{"id" => starship_id}]}] + } + } + } = json_response(conn, 200) + + assert ^starship_id = "#{starship.id}" + assert ^pilot_id = "#{List.first(starship.pilots).id}" + end + end +end diff --git a/test/swapi_web/graphql/queries/vehicle_queries_test.exs b/test/swapi_web/graphql/queries/vehicle_queries_test.exs new file mode 100644 index 0000000..774c67a --- /dev/null +++ b/test/swapi_web/graphql/queries/vehicle_queries_test.exs @@ -0,0 +1,161 @@ +defmodule SWAPIWeb.GraphQL.VehicleQueriesTest do + use SWAPIWeb.ConnCase + + import Ecto.Changeset + + import SWAPI.FilmsFixtures + import SWAPI.PeopleFixtures + import SWAPI.VehiclesFixtures + + alias SWAPI.Repo + alias SWAPI.Schemas.Vehicle + + setup do + vehicle = + vehicle_fixture() + |> Vehicle.changeset(%{transport: %{name: "Snowspeeder"}}) + |> put_assoc(:films, [film_fixture()]) + |> put_assoc(:pilots, [person_fixture()]) + |> Repo.update!() + + {:ok, %{vehicle: vehicle}} + end + + describe "allVehicles" do + test "returns all vehicles", %{conn: conn, vehicle: vehicle1} do + vehicle2 = vehicle_fixture() + + query = """ + query { + allVehicles { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "allVehicles" => [ + %{"id" => vehicle1_id}, + %{"id" => vehicle2_id} + ] + } + } = json_response(conn, 200) + + assert ^vehicle1_id = "#{vehicle1.id}" + assert ^vehicle2_id = "#{vehicle2.id}" + end + end + + describe "searchVehicles" do + test "returns matching vehicles", %{conn: conn, vehicle: vehicle} do + vehicle_fixture(%{name: "AT-AT"}) + + query = """ + query { + searchVehicles(searchTerms: ["Snowspeeder"]) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "searchVehicles" => [ + %{"id" => vehicle_id} + ] + } + } = json_response(conn, 200) + + assert ^vehicle_id = "#{vehicle.id}" + end + end + + describe "vehicle" do + test "returns vehicle when it exists", %{conn: conn, vehicle: vehicle} do + query = """ + query { + vehicle(id: #{vehicle.id}) { + id + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "vehicle" => %{"id" => vehicle_id} + } + } = json_response(conn, 200) + + assert ^vehicle_id = "#{vehicle.id}" + end + + test "loads nested fields", %{conn: conn, vehicle: vehicle} do + query = """ + query { + vehicle(id: #{vehicle.id}) { + id + films { + id + } + pilots { + id + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "vehicle" => %{ + "id" => vehicle_id, + "films" => [%{"id" => film_id}], + "pilots" => [%{"id" => pilot_id}] + } + } + } = json_response(conn, 200) + + assert ^vehicle_id = "#{vehicle.id}" + assert ^film_id = "#{List.first(vehicle.films).id}" + assert ^pilot_id = "#{List.first(vehicle.pilots).id}" + end + + test "handles recursive nesting", %{conn: conn, vehicle: vehicle} do + query = """ + query { + vehicle(id: #{vehicle.id}) { + id + pilots { + id + vehicles { + id + } + } + } + } + """ + + conn = get(conn, "/graphql", query: query) + + assert %{ + "data" => %{ + "vehicle" => %{ + "id" => vehicle_id, + "pilots" => [%{"id" => pilot_id, "vehicles" => [%{"id" => vehicle_id}]}] + } + } + } = json_response(conn, 200) + + assert ^vehicle_id = "#{vehicle.id}" + assert ^pilot_id = "#{List.first(vehicle.pilots).id}" + end + end +end From d79cd315bf759cb1cd428e9e36778aba870d8ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:57:29 +0100 Subject: [PATCH 15/19] Add GraphQL sandbox, move APi endpoint to /api/graphql --- lib/swapi_web/router.ex | 3 ++- test/swapi_web/graphql/queries/film_queries_test.exs | 10 +++++----- test/swapi_web/graphql/queries/person_queries_test.exs | 10 +++++----- test/swapi_web/graphql/queries/planet_queries_test.exs | 10 +++++----- .../swapi_web/graphql/queries/species_queries_test.exs | 10 +++++----- .../graphql/queries/starship_queries_test.exs | 10 +++++----- .../swapi_web/graphql/queries/vehicle_queries_test.exs | 10 +++++----- 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/swapi_web/router.ex b/lib/swapi_web/router.ex index 3c05991..0a71c5f 100644 --- a/lib/swapi_web/router.ex +++ b/lib/swapi_web/router.ex @@ -40,6 +40,7 @@ defmodule SWAPIWeb.Router do get "/", PageController, :home get "/postman", PageController, :postman get "/swaggerui", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi" + get "/graphiql", Absinthe.Plug.GraphiQL, schema: SWAPIWeb.GraphQL.Schema, default_url: "/api/graphql", interface: :playground end scope "/api" do @@ -57,7 +58,7 @@ defmodule SWAPIWeb.Router do get "/openapi", OpenApiSpex.Plug.RenderSpec, [] end - scope "/graphql" do + scope "/api/graphql" do pipe_through :graphql forward "/", Absinthe.Plug, schema: SWAPIWeb.GraphQL.Schema diff --git a/test/swapi_web/graphql/queries/film_queries_test.exs b/test/swapi_web/graphql/queries/film_queries_test.exs index 90d9fd9..7039192 100644 --- a/test/swapi_web/graphql/queries/film_queries_test.exs +++ b/test/swapi_web/graphql/queries/film_queries_test.exs @@ -39,7 +39,7 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -67,7 +67,7 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -91,7 +91,7 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -126,7 +126,7 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -164,7 +164,7 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ diff --git a/test/swapi_web/graphql/queries/person_queries_test.exs b/test/swapi_web/graphql/queries/person_queries_test.exs index 673f3aa..84679ee 100644 --- a/test/swapi_web/graphql/queries/person_queries_test.exs +++ b/test/swapi_web/graphql/queries/person_queries_test.exs @@ -39,7 +39,7 @@ defmodule SWAPIWeb.GraphQL.PersonQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -67,7 +67,7 @@ defmodule SWAPIWeb.GraphQL.PersonQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -91,7 +91,7 @@ defmodule SWAPIWeb.GraphQL.PersonQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -126,7 +126,7 @@ defmodule SWAPIWeb.GraphQL.PersonQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -164,7 +164,7 @@ defmodule SWAPIWeb.GraphQL.PersonQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ diff --git a/test/swapi_web/graphql/queries/planet_queries_test.exs b/test/swapi_web/graphql/queries/planet_queries_test.exs index 894649d..1fb7ec6 100644 --- a/test/swapi_web/graphql/queries/planet_queries_test.exs +++ b/test/swapi_web/graphql/queries/planet_queries_test.exs @@ -33,7 +33,7 @@ defmodule SWAPIWeb.GraphQL.PlanetQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -61,7 +61,7 @@ defmodule SWAPIWeb.GraphQL.PlanetQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -85,7 +85,7 @@ defmodule SWAPIWeb.GraphQL.PlanetQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -111,7 +111,7 @@ defmodule SWAPIWeb.GraphQL.PlanetQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -143,7 +143,7 @@ defmodule SWAPIWeb.GraphQL.PlanetQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ diff --git a/test/swapi_web/graphql/queries/species_queries_test.exs b/test/swapi_web/graphql/queries/species_queries_test.exs index 0c8e264..a50a139 100644 --- a/test/swapi_web/graphql/queries/species_queries_test.exs +++ b/test/swapi_web/graphql/queries/species_queries_test.exs @@ -35,7 +35,7 @@ defmodule SWAPIWeb.GraphQL.SpeciesQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -63,7 +63,7 @@ defmodule SWAPIWeb.GraphQL.SpeciesQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -87,7 +87,7 @@ defmodule SWAPIWeb.GraphQL.SpeciesQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -116,7 +116,7 @@ defmodule SWAPIWeb.GraphQL.SpeciesQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -150,7 +150,7 @@ defmodule SWAPIWeb.GraphQL.SpeciesQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ diff --git a/test/swapi_web/graphql/queries/starship_queries_test.exs b/test/swapi_web/graphql/queries/starship_queries_test.exs index ab52c12..b675945 100644 --- a/test/swapi_web/graphql/queries/starship_queries_test.exs +++ b/test/swapi_web/graphql/queries/starship_queries_test.exs @@ -33,7 +33,7 @@ defmodule SWAPIWeb.GraphQL.StarshipQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -61,7 +61,7 @@ defmodule SWAPIWeb.GraphQL.StarshipQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -85,7 +85,7 @@ defmodule SWAPIWeb.GraphQL.StarshipQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -111,7 +111,7 @@ defmodule SWAPIWeb.GraphQL.StarshipQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -143,7 +143,7 @@ defmodule SWAPIWeb.GraphQL.StarshipQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ diff --git a/test/swapi_web/graphql/queries/vehicle_queries_test.exs b/test/swapi_web/graphql/queries/vehicle_queries_test.exs index 774c67a..0d45d2d 100644 --- a/test/swapi_web/graphql/queries/vehicle_queries_test.exs +++ b/test/swapi_web/graphql/queries/vehicle_queries_test.exs @@ -33,7 +33,7 @@ defmodule SWAPIWeb.GraphQL.VehicleQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -61,7 +61,7 @@ defmodule SWAPIWeb.GraphQL.VehicleQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -85,7 +85,7 @@ defmodule SWAPIWeb.GraphQL.VehicleQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -111,7 +111,7 @@ defmodule SWAPIWeb.GraphQL.VehicleQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ @@ -143,7 +143,7 @@ defmodule SWAPIWeb.GraphQL.VehicleQueriesTest do } """ - conn = get(conn, "/graphql", query: query) + conn = get(conn, "/api/graphql", query: query) assert %{ "data" => %{ From 576c96e1799cbb7b2d980bb9bed5abee9190687a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:01:29 +0100 Subject: [PATCH 16/19] Fix formatting --- lib/swapi_web/router.ex | 6 +- .../graphql/queries/film_queries_test.exs | 70 +++++++++---------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/swapi_web/router.ex b/lib/swapi_web/router.ex index 0a71c5f..14b9367 100644 --- a/lib/swapi_web/router.ex +++ b/lib/swapi_web/router.ex @@ -40,7 +40,11 @@ defmodule SWAPIWeb.Router do get "/", PageController, :home get "/postman", PageController, :postman get "/swaggerui", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi" - get "/graphiql", Absinthe.Plug.GraphiQL, schema: SWAPIWeb.GraphQL.Schema, default_url: "/api/graphql", interface: :playground + + get "/graphiql", Absinthe.Plug.GraphiQL, + schema: SWAPIWeb.GraphQL.Schema, + default_url: "/api/graphql", + interface: :playground end scope "/api" do diff --git a/test/swapi_web/graphql/queries/film_queries_test.exs b/test/swapi_web/graphql/queries/film_queries_test.exs index 7039192..dc6c37a 100644 --- a/test/swapi_web/graphql/queries/film_queries_test.exs +++ b/test/swapi_web/graphql/queries/film_queries_test.exs @@ -42,13 +42,13 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do conn = get(conn, "/api/graphql", query: query) assert %{ - "data" => %{ - "allFilms" => [ - %{"id" => film1_id}, - %{"id" => film2_id} - ] - } - } = json_response(conn, 200) + "data" => %{ + "allFilms" => [ + %{"id" => film1_id}, + %{"id" => film2_id} + ] + } + } = json_response(conn, 200) assert ^film1_id = "#{film1.id}" assert ^film2_id = "#{film2.id}" @@ -70,12 +70,12 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do conn = get(conn, "/api/graphql", query: query) assert %{ - "data" => %{ - "searchFilms" => [ - %{"id" => film_id} - ] - } - } = json_response(conn, 200) + "data" => %{ + "searchFilms" => [ + %{"id" => film_id} + ] + } + } = json_response(conn, 200) assert ^film_id = "#{film.id}" end @@ -94,10 +94,10 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do conn = get(conn, "/api/graphql", query: query) assert %{ - "data" => %{ - "film" => %{"id" => film_id} - } - } = json_response(conn, 200) + "data" => %{ + "film" => %{"id" => film_id} + } + } = json_response(conn, 200) assert ^film_id = "#{film.id}" end @@ -129,17 +129,17 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do conn = get(conn, "/api/graphql", query: query) assert %{ - "data" => %{ - "film" => %{ - "id" => film_id, - "species" => [%{"id" => species_id}], - "starships" => [%{"id" => starship_id}], - "vehicles" => [%{"id" => vehicle_id}], - "characters" => [%{"id" => person_id}], - "planets" => [%{"id" => planet_id}] - } - } - } = json_response(conn, 200) + "data" => %{ + "film" => %{ + "id" => film_id, + "species" => [%{"id" => species_id}], + "starships" => [%{"id" => starship_id}], + "vehicles" => [%{"id" => vehicle_id}], + "characters" => [%{"id" => person_id}], + "planets" => [%{"id" => planet_id}] + } + } + } = json_response(conn, 200) assert ^film_id = "#{film.id}" assert ^species_id = "#{List.first(film.species).id}" @@ -167,13 +167,13 @@ defmodule SWAPIWeb.GraphQL.QueriesTests do conn = get(conn, "/api/graphql", query: query) assert %{ - "data" => %{ - "film" => %{ - "id" => film_id, - "characters" => [%{"id" => person_id, "films" => [%{"id" => film_id}]}] - } - } - } = json_response(conn, 200) + "data" => %{ + "film" => %{ + "id" => film_id, + "characters" => [%{"id" => person_id, "films" => [%{"id" => film_id}]}] + } + } + } = json_response(conn, 200) assert ^film_id = "#{film.id}" assert ^person_id = "#{List.first(film.characters).id}" From cdabc9df46c1c0e7bf188169db8d6e160792f4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:41:22 +0100 Subject: [PATCH 17/19] Add moduledocs --- lib/swapi/dataloader.ex | 4 ++++ lib/swapi_web/graphql/queries.ex | 4 ++++ lib/swapi_web/graphql/queries/film_queries.ex | 4 ++++ lib/swapi_web/graphql/queries/person_queries.ex | 4 ++++ lib/swapi_web/graphql/queries/planet_queries.ex | 4 ++++ lib/swapi_web/graphql/queries/species_queries.ex | 4 ++++ lib/swapi_web/graphql/queries/starship_queries.ex | 4 ++++ lib/swapi_web/graphql/queries/vehicle_queries.ex | 4 ++++ lib/swapi_web/graphql/schema.ex | 4 ++++ lib/swapi_web/graphql/types.ex | 4 ++++ lib/swapi_web/graphql/types/film.ex | 4 ++++ lib/swapi_web/graphql/types/person.ex | 4 ++++ lib/swapi_web/graphql/types/planet.ex | 4 ++++ lib/swapi_web/graphql/types/species.ex | 4 ++++ lib/swapi_web/graphql/types/starship.ex | 4 ++++ lib/swapi_web/graphql/types/vehicle.ex | 4 ++++ lib/swapi_web/graphql/util.ex | 4 ++++ 17 files changed, 68 insertions(+) diff --git a/lib/swapi/dataloader.ex b/lib/swapi/dataloader.ex index 27e525a..50d7b24 100644 --- a/lib/swapi/dataloader.ex +++ b/lib/swapi/dataloader.ex @@ -1,4 +1,8 @@ defmodule SWAPI.Dataloader do + @moduledoc """ + Dataloader for GraphQL + """ + def data(), do: Dataloader.Ecto.new(SWAPI.Repo, query: &query/2) def query(queryable, _params), do: queryable diff --git a/lib/swapi_web/graphql/queries.ex b/lib/swapi_web/graphql/queries.ex index 21b6c41..b577dcb 100644 --- a/lib/swapi_web/graphql/queries.ex +++ b/lib/swapi_web/graphql/queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries do + @moduledoc """ + GraphQL queries + """ + use Absinthe.Schema.Notation import_types(SWAPIWeb.GraphQL.Queries.FilmQueries) diff --git a/lib/swapi_web/graphql/queries/film_queries.ex b/lib/swapi_web/graphql/queries/film_queries.ex index 93ea9d8..99de57d 100644 --- a/lib/swapi_web/graphql/queries/film_queries.ex +++ b/lib/swapi_web/graphql/queries/film_queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries.FilmQueries do + @moduledoc """ + GraphQL queries for films + """ + use Absinthe.Schema.Notation alias SWAPIWeb.GraphQL.Resolvers.FilmResolver diff --git a/lib/swapi_web/graphql/queries/person_queries.ex b/lib/swapi_web/graphql/queries/person_queries.ex index b56ceff..98e7101 100644 --- a/lib/swapi_web/graphql/queries/person_queries.ex +++ b/lib/swapi_web/graphql/queries/person_queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries.PersonQueries do + @moduledoc """ + GraphQL queries for people + """ + use Absinthe.Schema.Notation alias SWAPIWeb.GraphQL.Resolvers.PersonResolver diff --git a/lib/swapi_web/graphql/queries/planet_queries.ex b/lib/swapi_web/graphql/queries/planet_queries.ex index 174862f..9fec459 100644 --- a/lib/swapi_web/graphql/queries/planet_queries.ex +++ b/lib/swapi_web/graphql/queries/planet_queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries.PlanetQueries do + @moduledoc """ + GraphQL queries for planets + """ + use Absinthe.Schema.Notation alias SWAPIWeb.GraphQL.Resolvers.PlanetResolver diff --git a/lib/swapi_web/graphql/queries/species_queries.ex b/lib/swapi_web/graphql/queries/species_queries.ex index 9b17691..7cdf06f 100644 --- a/lib/swapi_web/graphql/queries/species_queries.ex +++ b/lib/swapi_web/graphql/queries/species_queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries.SpeciesQueries do + @moduledoc """ + GraphQL queries for species + """ + use Absinthe.Schema.Notation alias SWAPIWeb.GraphQL.Resolvers.SpeciesResolver diff --git a/lib/swapi_web/graphql/queries/starship_queries.ex b/lib/swapi_web/graphql/queries/starship_queries.ex index 37bd1bf..860f4be 100644 --- a/lib/swapi_web/graphql/queries/starship_queries.ex +++ b/lib/swapi_web/graphql/queries/starship_queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries.StarshipQueries do + @moduledoc """ + GraphQL queries for starships + """ + use Absinthe.Schema.Notation alias SWAPIWeb.GraphQL.Resolvers.StarshipResolver diff --git a/lib/swapi_web/graphql/queries/vehicle_queries.ex b/lib/swapi_web/graphql/queries/vehicle_queries.ex index 0e0eff3..2b472d0 100644 --- a/lib/swapi_web/graphql/queries/vehicle_queries.ex +++ b/lib/swapi_web/graphql/queries/vehicle_queries.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Queries.VehicleQueries do + @moduledoc """ + GraphQL queries for vehicles + """ + use Absinthe.Schema.Notation alias SWAPIWeb.GraphQL.Resolvers.VehicleResolver diff --git a/lib/swapi_web/graphql/schema.ex b/lib/swapi_web/graphql/schema.ex index 7db9151..8caa2e3 100644 --- a/lib/swapi_web/graphql/schema.ex +++ b/lib/swapi_web/graphql/schema.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Schema do + @moduledoc """ + GraphQL schema + """ + use Absinthe.Schema import_types(SWAPIWeb.GraphQL.Types) diff --git a/lib/swapi_web/graphql/types.ex b/lib/swapi_web/graphql/types.ex index 1ff71fb..3dc0693 100644 --- a/lib/swapi_web/graphql/types.ex +++ b/lib/swapi_web/graphql/types.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types do + @moduledoc """ + GrahQL types + """ + use Absinthe.Schema.Notation import_types(Absinthe.Type.Custom) diff --git a/lib/swapi_web/graphql/types/film.ex b/lib/swapi_web/graphql/types/film.ex index 2dcfbc5..226fba6 100644 --- a/lib/swapi_web/graphql/types/film.ex +++ b/lib/swapi_web/graphql/types/film.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types.Film do + @moduledoc """ + GraphQL schema for films + """ + use Absinthe.Schema.Notation import Absinthe.Resolution.Helpers diff --git a/lib/swapi_web/graphql/types/person.ex b/lib/swapi_web/graphql/types/person.ex index 0139271..c7c2f78 100644 --- a/lib/swapi_web/graphql/types/person.ex +++ b/lib/swapi_web/graphql/types/person.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types.Person do + @moduledoc """ + GraphQL schema for people + """ + use Absinthe.Schema.Notation import Absinthe.Resolution.Helpers diff --git a/lib/swapi_web/graphql/types/planet.ex b/lib/swapi_web/graphql/types/planet.ex index 534dfea..4bf1e97 100644 --- a/lib/swapi_web/graphql/types/planet.ex +++ b/lib/swapi_web/graphql/types/planet.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types.Planet do + @moduledoc """ + GraphQL schema for planets + """ + use Absinthe.Schema.Notation import Absinthe.Resolution.Helpers diff --git a/lib/swapi_web/graphql/types/species.ex b/lib/swapi_web/graphql/types/species.ex index 785dcb5..175a373 100644 --- a/lib/swapi_web/graphql/types/species.ex +++ b/lib/swapi_web/graphql/types/species.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types.Species do + @moduledoc """ + GraphQL schema for species + """ + use Absinthe.Schema.Notation import Absinthe.Resolution.Helpers diff --git a/lib/swapi_web/graphql/types/starship.ex b/lib/swapi_web/graphql/types/starship.ex index e7f8996..4d9e5df 100644 --- a/lib/swapi_web/graphql/types/starship.ex +++ b/lib/swapi_web/graphql/types/starship.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do + @moduledoc """ + GraphQL schema for starships + """ + use Absinthe.Schema.Notation import Absinthe.Resolution.Helpers diff --git a/lib/swapi_web/graphql/types/vehicle.ex b/lib/swapi_web/graphql/types/vehicle.ex index 8968edb..4e9cf97 100644 --- a/lib/swapi_web/graphql/types/vehicle.ex +++ b/lib/swapi_web/graphql/types/vehicle.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do + @moduledoc """ + GraphQL schema for vehicles + """ + use Absinthe.Schema.Notation import Absinthe.Resolution.Helpers diff --git a/lib/swapi_web/graphql/util.ex b/lib/swapi_web/graphql/util.ex index 5879d15..9961cf6 100644 --- a/lib/swapi_web/graphql/util.ex +++ b/lib/swapi_web/graphql/util.ex @@ -1,4 +1,8 @@ defmodule SWAPIWeb.GraphQL.Util do + @moduledoc """ + Utility functions for the GraphQL API + """ + def transport_field_callback(transport, _parent, _args, %{path: [field | _]}) do case Map.get(transport, field.schema_node.identifier) do nil -> {:error, "Invalid transport field"} From b215460cf37ba6e6a27faf38e5f52177ddb9c69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:52:38 +0100 Subject: [PATCH 18/19] Fix string formatting --- lib/swapi_web/graphql/types/person.ex | 6 +++--- lib/swapi_web/graphql/types/planet.ex | 2 +- lib/swapi_web/graphql/types/species.ex | 2 +- lib/swapi_web/graphql/types/starship.ex | 4 ++-- lib/swapi_web/graphql/types/vehicle.ex | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/swapi_web/graphql/types/person.ex b/lib/swapi_web/graphql/types/person.ex index c7c2f78..3a0d6a4 100644 --- a/lib/swapi_web/graphql/types/person.ex +++ b/lib/swapi_web/graphql/types/person.ex @@ -18,13 +18,13 @@ defmodule SWAPIWeb.GraphQL.Types.Person do @desc "The birth year of the person, using the in-universe standard of **BBY** or **ABY** - Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is a battle that occurs at the end of Star Wars episode IV: A New Hope." field :birth_year, :string - @desc "The eye color of this person. Will be \"unknown\" if not known or \"n/a\" if the person does not have an eye." + @desc ~S(The eye color of this person. Will be "unknown" if not known or "n/a" if the person does not have an eye.) field :eye_color, :string - @desc "The gender of this person. Either \"Male\", \"Female\" or \"unknown\", \"n/a\" if the person does not have a gender." + @desc ~S(The gender of this person. Either "Male", "Female" or "unknown", "n/a" if the person does not have a gender.) field :gender, :string - @desc "The hair color of this person. Will be \"unknown\" if not known or \"n/a\" if the person does not have hair." + @desc ~S(The hair color of this person. Will be "unknown" if not known or "n/a" if the person does not have hair.) field :hair_color, :string @desc "The height of the person in centimeters." diff --git a/lib/swapi_web/graphql/types/planet.ex b/lib/swapi_web/graphql/types/planet.ex index 4bf1e97..9b9fe8d 100644 --- a/lib/swapi_web/graphql/types/planet.ex +++ b/lib/swapi_web/graphql/types/planet.ex @@ -24,7 +24,7 @@ defmodule SWAPIWeb.GraphQL.Types.Planet do @desc "The number of standard days it takes for this planet to complete a single orbit of its local star." field :orbital_period, :string - @desc "A number denoting the gravity of this planet, where \"1\" is normal or 1 standard G. \"2\" is twice or 2 standard Gs. \"0.5\" is half or 0.5 standard Gs." + @desc ~S(A number denoting the gravity of this planet, where "1" is normal or 1 standard G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs.) field :gravity, :string @desc "The average population of sentient beings inhabiting this planet." diff --git a/lib/swapi_web/graphql/types/species.ex b/lib/swapi_web/graphql/types/species.ex index 175a373..68fed31 100644 --- a/lib/swapi_web/graphql/types/species.ex +++ b/lib/swapi_web/graphql/types/species.ex @@ -15,7 +15,7 @@ defmodule SWAPIWeb.GraphQL.Types.Species do @desc "The name of this species." field :name, :string - @desc "The classification of this species, such as \"mammal\" or \"reptile\"." + @desc ~S(The classification of this species, such as "mammal" or "reptile".) field :classification, :string @desc "The designation of this species, such as \"sentient\"." diff --git a/lib/swapi_web/graphql/types/starship.ex b/lib/swapi_web/graphql/types/starship.ex index 4d9e5df..4ddb63d 100644 --- a/lib/swapi_web/graphql/types/starship.ex +++ b/lib/swapi_web/graphql/types/starship.ex @@ -18,12 +18,12 @@ defmodule SWAPIWeb.GraphQL.Types.Starship do resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end - @desc "The model or official name of this starship. Such as \"T-65 X-wing\" or \"DS-1 Orbital Battle Station\"." + @desc ~S(The model or official name of this starship. Such as "T-65 X-wing" or "DS-1 Orbital Battle Station".) field :model, :string do resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end - @desc "The class of this starship, such as \"Starfighter\" or \"Deep Space Mobile Battlestation\"" + @desc ~S(The class of this starship, such as "Starfighter" or "Deep Space Mobile Battlestation") field :starship_class, :string @desc "The manufacturer of this starship. Comma separated if more than one." diff --git a/lib/swapi_web/graphql/types/vehicle.ex b/lib/swapi_web/graphql/types/vehicle.ex index 4e9cf97..eef0db0 100644 --- a/lib/swapi_web/graphql/types/vehicle.ex +++ b/lib/swapi_web/graphql/types/vehicle.ex @@ -13,7 +13,7 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do @desc "A unique ID for this vehicle." field :id, :id - @desc "The name of this vehicle. The common name, such as \"Sand Crawler\" or \"Speeder bike\"." + @desc ~S(The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder bike".) field :name, :string do resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end @@ -23,7 +23,7 @@ defmodule SWAPIWeb.GraphQL.Types.Vehicle do resolve(dataloader(SWAPI.Dataloader, :transport, callback: &transport_field_callback/4)) end - @desc "The class of this vehicle, such as \"Wheeled\" or \"Repulsorcraft\"." + @desc ~S(The class of this vehicle, such as "Wheeled" or "Repulsorcraft".) field :vehicle_class, :string @desc "The manufacturer of this vehicle. Comma separated if more than one." From 43785bb432fd6f8867e716aeb9a8822fd951baa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Hrdli=C4=8Dka?= <13226155+dhrdlicka@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:53:06 +0100 Subject: [PATCH 19/19] Fix remaining credo issues --- lib/swapi/dataloader.ex | 2 +- lib/swapi_web/graphql/resolvers/species_resolver.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/swapi/dataloader.ex b/lib/swapi/dataloader.ex index 50d7b24..25884c9 100644 --- a/lib/swapi/dataloader.ex +++ b/lib/swapi/dataloader.ex @@ -3,7 +3,7 @@ defmodule SWAPI.Dataloader do Dataloader for GraphQL """ - def data(), do: Dataloader.Ecto.new(SWAPI.Repo, query: &query/2) + def data, do: Dataloader.Ecto.new(SWAPI.Repo, query: &query/2) def query(queryable, _params), do: queryable end diff --git a/lib/swapi_web/graphql/resolvers/species_resolver.ex b/lib/swapi_web/graphql/resolvers/species_resolver.ex index 78f1075..79c7def 100644 --- a/lib/swapi_web/graphql/resolvers/species_resolver.ex +++ b/lib/swapi_web/graphql/resolvers/species_resolver.ex @@ -3,8 +3,8 @@ defmodule SWAPIWeb.GraphQL.Resolvers.SpeciesResolver do Species resolver. """ - alias SWAPI.Species alias SWAPI.Schemas.Species, as: SpeciesSchema + alias SWAPI.Species @spec all(map, map) :: {:ok, list(SpeciesSchema.t())} | {:error, any} def all(_args, _info) do