Skip to content

Commit

Permalink
API Base Setup
Browse files Browse the repository at this point in the history
  • Loading branch information
maennchen committed Mar 1, 2022
1 parent bce22ac commit 8494e1e
Show file tree
Hide file tree
Showing 29 changed files with 663 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[
import_deps: [:ecto, :phoenix],
import_deps: [:ecto, :phoenix, :absinthe],
inputs: [
"{mix,.formatter}.exs",
"*.{ex,exs}",
Expand Down
5 changes: 4 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ config :athena,
ecto_repos: [Athena.Repo],
generators: [context_app: :athena, binary_id: true]

config :athena, Athena.Repo, migration_primary_key: [id: :uuid, type: :binary_id]
config :athena, Athena.Repo,
migration_primary_key: [id: :uuid, type: :binary_id],
migration_foreign_key: [column: :id, type: :binary_id],
migration_timestamps: [type: :utc_datetime_usec]

# Configures the endpoint
config :athena, AthenaWeb.Endpoint,
Expand Down
5 changes: 5 additions & 0 deletions lib/athena.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ defmodule Athena do
if it comes from the database, an external API or others.
"""

@doc false
def model do
quote do
use Ecto.Schema

import Ecto.Changeset

alias Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@timestamps_opts type: :utc_datetime_usec

@type association(type) :: Ecto.Association.NotLoaded.t() | type
end
Expand Down
4 changes: 2 additions & 2 deletions lib/athena/inventory/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ defmodule Athena.Inventory.Event do
locations: association([Location.t()]),
item_groups: association([ItemGroup.t()]),
items: association([Item.t()]),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}

schema "events" do
Expand Down
4 changes: 2 additions & 2 deletions lib/athena/inventory/item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ defmodule Athena.Inventory.Item do
inverse: boolean,
item_group: association(ItemGroup.t()),
event: association(Event.t()),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}

schema "items" do
Expand Down
4 changes: 2 additions & 2 deletions lib/athena/inventory/item_group.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ defmodule Athena.Inventory.ItemGroup do
event: association(Event.t()),
location: association(Location.t()),
items: association([Item.t()]),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}

schema "item_groups" do
Expand Down
4 changes: 2 additions & 2 deletions lib/athena/inventory/location.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ defmodule Athena.Inventory.Location do
items: association([Item.t()]),
movements_in: association([Movement.t()]),
movements_out: association([Movement.t()]),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}

schema "locations" do
Expand Down
4 changes: 2 additions & 2 deletions lib/athena/inventory/movement.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ defmodule Athena.Inventory.Movement do
item_group: association(ItemGroup.t()),
source_location: association(Location.t() | nil),
destination_location: association(Location.t() | nil),
inserted_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t()
inserted_at: DateTime.t(),
updated_at: DateTime.t()
}

schema "movements" do
Expand Down
27 changes: 27 additions & 0 deletions lib/athena_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,33 @@ defmodule AthenaWeb do
end
end

@doc false
@spec subschema :: Macro.t()
def subschema do
quote do
use Absinthe.Schema.Notation
use Absinthe.Relay.Schema.Notation, :modern

import Absinthe.Resolution.Helpers, only: [dataloader: 1]
# import AbsintheErrorPayload.Payload

alias __MODULE__.Resolver

alias AthenaWeb.Schema.Dataloader, as: RepoDataLoader
end
end

@doc false
@spec resolver :: Macro.t()
def resolver do
quote do
import Absinthe.Resolution.Helpers
import Ecto.Query

alias Athena.Repo
end
end

@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
Expand Down
2 changes: 1 addition & 1 deletion lib/athena_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ defmodule AthenaWeb.Endpoint do
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]

plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
parsers: [:urlencoded, :multipart, :json, Absinthe.Plug.Parser],
pass: ["*/*"],
json_decoder: Phoenix.json_library()

Expand Down
22 changes: 22 additions & 0 deletions lib/athena_web/exception.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defprotocol AthenaWeb.Exception do
@fallback_to_any true

@spec result(exception :: Exception.t()) :: {:ok, term} | {:error, String.t()} | :unknown
def result(exception)
end

defimpl AthenaWeb.Exception, for: Any do
@spec result(exception :: Exception.t()) :: {:ok, term} | {:error, String.t()} | :unknown
def result(%{result: result} = _exception), do: result
def result(_exception), do: :unknown
end

defimpl AthenaWeb.Exception, for: Ecto.NoResultsError do
@spec result(exception :: Exception.t()) :: {:ok, term} | {:error, String.t()} | :unknown
def result(_exception), do: {:ok, nil}
end

defimpl AthenaWeb.Exception, for: Ecto.Query.CastError do
@spec result(exception :: Exception.t()) :: {:ok, term} | {:error, String.t()} | :unknown
def result(_exception), do: {:ok, nil}
end
85 changes: 85 additions & 0 deletions lib/athena_web/middleware/safe.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
defmodule AthenaWeb.Middleware.Safe do
@moduledoc false

alias AthenaWeb.Endpoint

require Logger

@spec add_error_handling(spec :: Absinthe.Middleware.spec()) :: Absinthe.Middleware.spec()
def add_error_handling(spec), do: &(spec |> to_fun(&1, &2) |> exec_safely(&1))

# Absinthe Node Error
defp to_fun(
{{Absinthe.Relay.Node, :global_id_resolver}, nil},
%{state: :resolved} = resolution,
_config
) do
fn -> resolution end
end

defp to_fun({{module, function}, config}, resolution, _config) do
fn -> apply(module, function, [resolution, config]) end
end

defp to_fun({module, config}, resolution, _config) do
fn -> module.call(resolution, config) end
end

defp to_fun(module, resolution, config) when is_atom(module) do
fn -> module.call(resolution, config) end
end

defp to_fun(function, resolution, config) when is_function(function, 2) do
fn -> function.(resolution, config) end
end

defp exec_safely(function, resolution) when is_function(function, 0) do
function.()
catch
kind, reason ->
full_exception = Exception.format(kind, reason, __STACKTRACE__)
result = AthenaWeb.Exception.result(reason)

result =
case {result, Endpoint.config(:debug_errors, false)} do
{{:ok, _} = result, _} ->
result

{{:error, error}, true} ->
{:error,
"""
#{inspect(error, pretty: true)}
DEBUG:
#{full_exception}
"""}

{{:error, error}, false} ->
{:error, error}

{:unknown, true} ->
Logger.error("""
Unknown exception catched:
#{full_exception}
""")

{:error,
"""
unkown error
DEBUG:
#{full_exception}
"""}

{:unknown, false} ->
Logger.error("""
Unknown exception catched:
#{full_exception}
""")

{:error, "unkown error"}
end

Absinthe.Resolution.put_result(resolution, result)
end
end
16 changes: 16 additions & 0 deletions lib/athena_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ defmodule AthenaWeb.Router do

@subresource_actions [:index, :new, :create]

pipeline :api do
plug :accepts, ["json"]
end

pipeline :browser do
plug :accepts, ["html"]

Expand Down Expand Up @@ -82,6 +86,18 @@ defmodule AthenaWeb.Router do
get "/", Redirector, to: "/admin/events"
end

scope "/api" do
pipe_through [:api]

forward(
"/",
Absinthe.Plug.GraphiQL,
schema: AthenaWeb.Schema,
socket: AthenaWeb.UserSocket,
interface: :playground
)
end

defp auth(conn, _opts),
do: Plug.BasicAuth.basic_auth(conn, Application.fetch_env!(:athena, Plug.BasicAuth))
end
66 changes: 66 additions & 0 deletions lib/athena_web/schema.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
defmodule AthenaWeb.Schema do
@moduledoc """
Root GraphQL Schema
"""

use Absinthe.Schema
use Absinthe.Relay.Schema, :modern

alias AthenaWeb.Middleware.Safe
alias AthenaWeb.Schema.Dataloader, as: RepoDataLoader
alias AthenaWeb.Schema.Resolver

@impl Absinthe.Schema
@spec context(context :: map) :: map
def context(context),
do:
Map.put(
context,
:loader,
Dataloader.add_source(Dataloader.new(), RepoDataLoader, RepoDataLoader.data())
)

@impl Absinthe.Schema
@spec plugins :: [atom]
def plugins, do: [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]

@impl Absinthe.Schema
@spec middleware(
[Absinthe.Middleware.spec(), ...],
Absinthe.Type.Field.t(),
Absinthe.Type.Object.t()
) :: [Absinthe.Middleware.spec(), ...]
def middleware(middleware, _field, _object),
do: Enum.map(middleware, &Safe.add_error_handling/1)

import_types Absinthe.Plug.Types
# import_types AbsintheErrorPayload.ValidationMessageTypes
import_types AthenaWeb.Schema.Event
import_types AthenaWeb.Schema.Scalar.Date
import_types AthenaWeb.Schema.Scalar.Datetime
import_types AthenaWeb.Schema.Scalar.Map
import_types AthenaWeb.Schema.Scalar.Ok
import_types AthenaWeb.Schema.Scalar.URI

node interface do
end

interface :resource do
field :id, non_null(:id)
field :inserted_at, non_null(:datetime)
field :updated_at, non_null(:datetime)

interface :node
end

query do
node field do
resolve(&Resolver.node/2)
end

import_fields :event_queries
end

# mutation do
# end
end
10 changes: 10 additions & 0 deletions lib/athena_web/schema/dataloader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule AthenaWeb.Schema.Dataloader do
@moduledoc """
Absinthe Dataloader
"""

alias Athena.Repo

@spec data :: Dataloader.Ecto.t()
def data, do: Dataloader.Ecto.new(Repo)
end
33 changes: 33 additions & 0 deletions lib/athena_web/schema/event.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule AthenaWeb.Schema.Event do
@moduledoc false

use AthenaWeb, :subschema

alias Athena.Inventory.Event

node object(:event) do
field :name, non_null(:string)

# connection field :locations, node_type: :location
# connection field :item_groups, node_type: :item_group
# connection field :items, node_type: :item

field :inserted_at, non_null(:datetime)
field :updated_at, non_null(:datetime)

is_type_of(&match?(%Event{}, &1))

interface :resource
end

connection(node_type: :event, non_null: true)

object :event_queries do
@desc "Get Event By ID"
field :event, :event do
arg :id, non_null(:id)

resolve(&Resolver.event/3)
end
end
end
9 changes: 9 additions & 0 deletions lib/athena_web/schema/event/resolver.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule AthenaWeb.Schema.Event.Resolver do
@moduledoc false

alias Athena.Inventory

@spec event(parent :: term(), args :: map(), resolution :: Absinthe.Resolution.t()) ::
{:ok, term()} | {:error, term()}
def event(_parent, %{id: id}, _resolution), do: {:ok, Inventory.get_event!(id)}
end

0 comments on commit 8494e1e

Please sign in to comment.