Как выполнять запросы GraphQL в контроллерах Elixir/Phoenix, не удаляя сгенерированные веб-страницы CRUD?

Я создал приложение Phoenix в Elixir. Я использовал инструмент генерации для создания всей грубой функциональности и грубых страниц. В настоящее время он использует:

def index(conn, _params) do
  data = Repo.all(Object)
  render(conn, "index.html", data: data)
end

Как я могу заменить это реализацией GraphQL, потому что в настоящее время у меня есть возможность передавать запросы GraphQL через указанный URL, например. получать все записи из таблицы. В документации рассказывается об использовании плагина absinthe_phoenix и его добавлении в конвейер. В результате мы просто заменяем текущие веб-страницы, которые у меня есть, и запрашивают URL-адрес, причем все текущие страницы являются теми, которые были созданы Phoenix, когда вы запускаете команду scaffholding для генерации crud и схемы базы данных.

Мне нужно сохранить все эти грубые страницы, но чтобы они выполняли запросы GrapQL. Так что на странице, которая отображает все записи из базы данных, мне нужно, чтобы вместо запуска

data = Repo.all(Object)

должно бежать

{
  objects{
    field1,
    field2
  }
}

чтобы получить все данные. Как запустить запросы GraphQL в контроллерах?

Это запрос, который мне нужно выполнить в моей схеме GraphQL

query do
    @doc """
    Returns all the records from a database
    """
    field :objects, list_of(:object) do
        resolve &Billingplus.ObjectResolver.all/2
    end
end

0 ответов

Вам нужно создать ссылки в вашей схеме GQL и иметь функции распознавателя для разных вещей. Вы не помещаете это в свой контроллер. Я покажу вам фронтальный пример "вещи", реализованной из БД в Абсент.

REST и GQL - это разные парадигмы / интерфейсы.

У вас еще есть работающий GQL в вашем проекте? Покажите, что вы сделали, чтобы мы могли дать совет.

Пример мутации:

mutation do
    @desc "Create an OAuth2 client"
    field :create_oauth2_client, :oauth2_client do
      arg(:app_id, non_null(:uuid4))
      arg(:client_id, non_null(:string))
      arg(:client_secret, non_null(:string))
      arg(:oauth2_provider_id, non_null(:uuid4))

      resolve(&Resolvers.OAuth2Client.create_oauth2_client/3)
    end
end

Пример запроса:

query do
    @desc "Get an OAuth2 client"
    field :oauth2_client, :oauth2_client do
      arg(:id, non_null(:uuid4))
      resolve(&Resolvers.OAuth2Client.get_oauth2_client/3)
    end
end

пример объекта схемы:

defmodule ApiWeb.Schema.OAuth2Client do
  use Absinthe.Schema.Notation

  alias Api.Auth.Apps
  alias Api.Auth.OAuth2Providers
  alias Api.Util

  @desc "An OAuth2 client"
  object :oauth2_client do
    field(:id, :uuid4)
    field(:client_id, :string)
    field(:client_secret, :string)

    field(:app, :app,
      resolve: fn oauth2_client, _, _ ->
        Apps.get_app(oauth2_client.app_id)
        |> Util.handle_not_found_error_and_wrap("App not found.")
      end
    )

    field(:oauth2_provider, :oauth2_provider,
      resolve: fn oauth2_client, _, _ ->
        OAuth2Providers.get_oauth2_provider(oauth2_client.oauth2_provider_id)
        |> Util.handle_not_found_error_and_wrap("OAuth2Provider not found.")
      end
    )
  end
end

пример резольвера:

defmodule ApiWeb.Resolvers.OAuth2Client do
  alias Api.Auth.OAuth2Clients

  #
  # QUERIES
  #
  def get_oauth2_client(_parent, %{id: id}, _resolution) do
    OAuth2Clients.get_oauth2_client(id)
  end

  def get_oauth2_clients_by_app(_parent, %{app_id: app_id}, _resolution) do
    OAuth2Clients.get_oauth2_clients_by_app(app_id)
  end

  #
  # MUTATIONS
  #
  def create_oauth2_client(_parent, params, _resolution) do
    OAuth2Clients.create_oauth2_client(%{
      app_id: params.app_id,
      oauth2_provider_id: params.oauth2_provider_id,
      client_id: params.client_id,
      client_secret: params.client_secret
    })
  end
end

Пример контекста:

defmodule Api.Auth.OAuth2Clients do
  @moduledoc """
  The OAuth2Clients context.
  """

  import Ecto.Query, warn: false

  alias Api.Repo
  alias Api.Auth.OAuth2Clients.OAuth2Client
  alias Api.Util

  @doc """
  Returns the list of OAuth2Clients.

  ## Examples

      iex> list_oauth2_clients()
      {:ok, [%OAuth2Client{}, ...]}

  """
  def list_oauth2_clients do
    Repo.all(OAuth2Client)
    |> Util.handle_not_found_error_and_wrap("No OAuth2Clients found.")
  end

  @doc """
  Gets a single OAuth2Client.

  Returns `{:error, %NotFoundError{}}` if the OAuth2Client does not exist.

  ## Examples

      iex> get_oauth2_client(123)
      {:ok, %OAuth2Client{}}

      iex> get_oauth2_client(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_client(id) do
    Repo.get(OAuth2Client, id)
    |> Util.handle_not_found_error_and_wrap("OAuth2Client not found.")
  end

  @doc """
  Gets a single OAuth2Client by `client_id` and `provider_id`.

  Returns `{:error, %NotFoundError{}}` if the OAuth2Client does not exist.

  ## Examples

      iex> get_oauth2_client_by_client_and_provider_id(123)
      {:ok, %OAuth2Client{}}

      iex> get_oauth2_client_by_client_and_provider_id(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_client_by_client_and_provider_id(client_id, provider_id) do
    from(o in OAuth2Client,
      where: o.client_id == ^client_id and o.oauth2_provider_id == ^provider_id
    )
    |> Repo.one()
    |> Util.handle_not_found_error_and_wrap("OAuth2Client not found.")
  end

  @doc """
  Gets a list of OAuth2Client by App.

  Returns `{:error, %NotFoundError{}}` if there are no OAuth2Client to return.

  ## Examples

      iex> get_oauth2_clients_by_app(123)
      {:ok, [%OAuth2Client{}, ...]}

      iex> get_oauth2_clients_by_app(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_clients_by_app(app_id) do
    from(o in OAuth2Client,
      where: o.app_id == ^app_id
    )
    |> Repo.all()
    |> Util.handle_not_found_error_and_wrap("App not found.")
  end

  @doc """
  Gets an OAuth2Client by `App` and `Provider`

  Returns `{:error, %NotFoundError{}}` if there is no `OAuth2Client` to return.

  ## Examples

      iex> get_oauth2_clients_by_app_and_provider(123)
      {:ok, [%OAuth2Client{}, ...]}

      iex> get_oauth2_clients_by_app_and_provider(456)
      {:error, %NotFoundError{}}

  """
  def get_oauth2_client_by_app_and_provider(app_id, provider_id) do
    from(o in OAuth2Client,
      where: o.app_id == ^app_id and o.oauth2_provider_id == ^provider_id
    )
    |> Repo.one()
    |> Util.handle_not_found_error_and_wrap("App not found.")
  end

  @doc """
  Creates an OAuth2Client.

  ## Examples

      iex> create_oauth2_client(%{field: value})
      {:ok, %OAuth2Client{}}

      iex> create_oauth2_client(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_oauth2_client(attrs) do
    %OAuth2Client{}
    |> OAuth2Client.create(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates an OAuth2Client.

  ## Examples

      iex> update_oauth2_client(oauth2_client, %{field: new_value})
      {:ok, %OAuth2Client{}}

      iex> update_oauth2_client(oauth2_client, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_oauth2_client(%OAuth2Client{} = oauth2_client, attrs) do
    oauth2_client
    |> OAuth2Client.update(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a OAuth2Client.

  ## Examples

      iex> delete_oauth2_client(oauth2_client)
      {:ok, %OAuth2Client{}}

      iex> delete_oauth2_client(oauth2_client)
      {:error, %Ecto.Changeset{}}

  """
  def delete_oauth2_client(%OAuth2Client{} = oauth2_client) do
    Repo.delete(oauth2_client)
  end
end

Пример схемы / модели:

defmodule Api.Auth.OAuth2Clients.OAuth2Client do
  use Ecto.Schema
  import Ecto.Changeset

  alias Api.Auth.Apps.App
  alias Api.Auth.OAuth2Providers.OAuth2Provider

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "oauth2_clients" do
    field(:client_id, :string)
    field(:client_secret, :string)

    belongs_to(:app, App)
    belongs_to(:oauth2_provider, OAuth2Provider)

    timestamps()
  end

  def create(app, attrs) do
    app
    |> cast(attrs, [:client_id, :client_secret, :oauth2_provider_id, :app_id])
    |> validate_required([:client_id, :client_secret, :oauth2_provider_id, :app_id])
    |> foreign_key_constraint(:app_id)
    |> foreign_key_constraint(:oauth2_provider_id)
  end

  def update(app, attrs) do
    app
    |> cast(attrs, [:client_id, :client_secret, :oauth2_provider_id, :app_id])
    |> foreign_key_constraint(:app_id)
    |> foreign_key_constraint(:oauth2_provider_id)
  end
end

пример миграции:

defmodule Api.Repo.Migrations.CreateOAuth2Clients do
  use Ecto.Migration

  def change do
    create table(:oauth2_clients, primary_key: false) do
      add(:id, :binary_id, primary_key: true)
      add(:client_id, :string, null: false)
      add(:client_secret, :string, null: false)
      add(:oauth2_provider_id, references(:oauth2_providers, type: :binary_id), null: false)
      add(:app_id, references(:apps, type: :binary_id), null: false)
      timestamps()
    end
  end

  def up do
    create(
      constraint(:owners, :user_or_organization,
        check:
          "((organization_id is not null and user_id is null) or (organization_id is null and user_id is not null))"
      )
    )
  end

  def down do
    drop(constraint(:owners, :user_or_organization))
  end
end

Это где вы запутались. Вместо того, чтобы ссылаться на контроллер в вашем роутере, вы указываете конечную точку GQL и запрашиваете там свой бэкэнд.

defmodule ApiWeb.Router do
  use ApiWeb, :router

  alias ApiWeb.OAuthController

  pipeline :api do
    plug(Plug.Parsers,
      parsers: [:json, Absinthe.Plug.Parser],
      pass: ["*/*"],
      json_decoder: Jason
    )

    plug(:accepts, ["json"])
  end

  scope "/" do
    pipe_through(:api)

    get("/oauth2", OAuthController, :callback)

    post("/graphql", Absinthe.Plug, schema: ApiWeb.Schema)

    forward("/graphiql", Absinthe.Plug.GraphiQL,
      schema: ApiWeb.Schema,
      json_codec: Jason
    )
  end
end

Есть много разных компонентов для реализации Absinthe в проекте Phoenix. Мне потребовалось некоторое время, чтобы обернуть голову - в основном потому, что определение объекта немного странно, и вы можете создавать псевдо виртуальные поля. Реализация способности разрешать структурные ссылки внутри запроса также поначалу немного сбивала с толку.

Другие вопросы по тегам