Как выполнять запросы 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. Мне потребовалось некоторое время, чтобы обернуть голову - в основном потому, что определение объекта немного странно, и вы можете создавать псевдо виртуальные поля. Реализация способности разрешать структурные ссылки внутри запроса также поначалу немного сбивала с толку.