diff --git a/README.md b/README.md index 2c63056..b708aba 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ -# What are we building? +# Getting started with Absinthe -This branch contains a very simple Phoenix app which displays a gallery of faces -imported from Gitbhub users. Shout out to -[Eduardo](https://github.com/edgurgel) for the cool -[Tentacat](https://hex.pm/packages/tentacat) package which made using the Github -API so easy. +[Absinthe](https://absinthe-graphql.org/) is an open source implementation of +the GraphQL server specification for Elixir. Note that I said "Elixir" there +and now "Phoenix". Absinthe doesn't need Phoenix, but we're going to use it in +this example because my guess is that not many people are making purely GraphQL +services and most of us are probably bolting it on to existing sites or +services. -[Demo](http://localhost:4000) +Absinthe has [truly amazing docs](https://hexdocs.pm/absinthe/overview.html). +Seriously. They're so great. You should check them out. -Now move on to the `step-3` branch. +In this branch we've added Absinthe to our project, and configured a type and a +schema for our list of people, and a migration which can import new people. + +Look at: + +* `mix.exs` +* `lib/faces_web/router.ex` +* `lib/faces_web/schema/schema.ex` +* `lib/faces_web/schema/person.ex` +* `lib/faces_web/resolvers/people.ex` + +[Demo](http://localhost:4000/graphiql) diff --git a/lib/faces_web/resolvers/people.ex b/lib/faces_web/resolvers/people.ex new file mode 100644 index 0000000..28a1d8f --- /dev/null +++ b/lib/faces_web/resolvers/people.ex @@ -0,0 +1,30 @@ +defmodule FacesWeb.Resolvers.People do + alias Faces.Gallery + + @doc """ + This is the resolver callback for Absinthe to find a list of all people. + + The arguments are: + + * `parent`, any parent object which Absinthe things we're related to. + * `args`, any arguments passed to the query. + * `resolution`, + """ + def list_people(_parent, _args, _resolution) do + {:ok, Gallery.list_people()} + end + + @doc """ + This is the resolver callback Absinthe uses to create a person. + + The arguments are: + + * `parent` any parent object which Absinthe things we're related to. + * `args` a map of arguments passed to the query. + * `context` a context object which can be used for things like + authentication, etc. + """ + def create_person(_parent, %{username: username}, _context) do + Gallery.import_user(username) + end +end diff --git a/lib/faces_web/router.ex b/lib/faces_web/router.ex index 590ab90..8d5d6c4 100644 --- a/lib/faces_web/router.ex +++ b/lib/faces_web/router.ex @@ -17,11 +17,14 @@ defmodule FacesWeb.Router do # Use the default browser stack pipe_through(:browser) - resources("/", FaceController) + resources("/", FaceController, only: [:index, :create]) end # Other scopes may use custom stacks. # scope "/api", FacesWeb do # pipe_through :api # end + + forward("/api", Absinthe.Plug, schema: FacesWeb.Schema) + forward("/graphiql", Absinthe.Plug.GraphiQL, schema: FacesWeb.Schema) end diff --git a/lib/faces_web/schema/person.ex b/lib/faces_web/schema/person.ex new file mode 100644 index 0000000..ce27704 --- /dev/null +++ b/lib/faces_web/schema/person.ex @@ -0,0 +1,27 @@ +defmodule FacesWeb.Schema.Person do + use Absinthe.Schema.Notation + + @desc "A person whose face we want to see" + object :person do + @desc "A unique identifier for this person" + field(:id, :id) + + @desc "The person's Github username" + field(:username, :string) + + @desc "The person's name as per Github" + field(:name, :string) + + @desc "The person's location as per Github" + field(:location, :string) + + @desc "The URL of the person's Github avatar image" + field(:avatar_url, :string) + + @desc "When this user was first imported into the faces app" + field(:inserted_at, :datetime) + + @desc "When this user was last updated in the faces app" + field(:updated_at, :datetime) + end +end diff --git a/lib/faces_web/schema/schema.ex b/lib/faces_web/schema/schema.ex new file mode 100644 index 0000000..bba3005 --- /dev/null +++ b/lib/faces_web/schema/schema.ex @@ -0,0 +1,22 @@ +defmodule FacesWeb.Schema do + use Absinthe.Schema + alias FacesWeb.Resolvers + + import_types(Absinthe.Type.Custom) + import_types(FacesWeb.Schema.Person) + + query do + @desc "List all people" + field :people, list_of(:person) do + resolve(&Resolvers.People.list_people/3) + end + end + + mutation do + @desc "Import a user from Github" + field :import_person, type: :person do + arg(:username, non_null(:string)) + resolve(&Resolvers.People.create_person/3) + end + end +end diff --git a/mix.exs b/mix.exs index 72d2edc..57a5a57 100644 --- a/mix.exs +++ b/mix.exs @@ -41,7 +41,9 @@ defmodule Faces.Mixfile do {:phoenix_live_reload, "~> 1.0", only: :dev}, {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, - {:tentacat, "~> 0.9.0"} + {:tentacat, "~> 0.9.0"}, + {:absinthe_plug, "~> 1.4"}, + {:poison, "~> 2.1.0", override: true} ] end diff --git a/mix.lock b/mix.lock index 4c3edb5..7285596 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,6 @@ %{ + "absinthe": {:hex, :absinthe, "1.4.10", "9f8d0c34dfcfd0030d3a3f123c7501e99ab59651731387289dad5885047ebb2a", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "absinthe_plug": {:hex, :absinthe_plug, "1.4.2", "01bf16f0a637869bcc0a1919935f08ff853501004e7549ddaa3a7788deb48965", [:mix], [{:absinthe, "~> 1.4", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, @@ -23,7 +25,7 @@ "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.3", "1d178429fc8950b12457d09c6afec247bfe1fcb6f36209e18fbb0221bdfe4d41", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, "plug": {:hex, :plug, "1.5.0", "224b25b4039bedc1eac149fb52ed456770b9678bbf0349cdd810460e1e09195b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "poison": {:hex, :poison, "2.1.0", "f583218ced822675e484648fa26c933d621373f01c6c76bd00005d7bd4b82e27", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},