chore: add gaiia library dep, remove stale honeybadger/tidewave/cbor
- Add vendored Gaiia library (path: vendor/gaiia) with auto-generated GraphQL queries and mutations from the Gaiia API schema - Add startAddInventoryItemsJob + updateInventoryItem mutations - Remove `:cbor` (was unused — no references anywhere) - Remove stale Honeybadger filter module, tests, router plug, and config - Remove stale Tidewave plug from endpoint (dev-only, dep already gone) - Unlock leftover deps from lock file
This commit is contained in:
parent
485748d253
commit
1f9fe2ee29
23 changed files with 12130 additions and 438 deletions
|
|
@ -25,13 +25,6 @@ config :esbuild,
|
|||
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}
|
||||
]
|
||||
|
||||
config :honeybadger,
|
||||
api_key: "hbp_xe5xMnpLZ2XJsXQJujoEkgCmiqCfwa0uYA3Y",
|
||||
environment_name: config_env(),
|
||||
insights_enabled: true,
|
||||
use_logger: true,
|
||||
filter: Towerops.HoneybadgerFilter
|
||||
|
||||
# Configure Elixir's Logger
|
||||
config :logger, :default_formatter,
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
import Config
|
||||
|
||||
# Disable Honeybadger in development
|
||||
config :honeybadger,
|
||||
environment_name: :dev,
|
||||
exclude_envs: [:dev],
|
||||
# Don't use custom filter in dev since Honeybadger is excluded
|
||||
filter: Honeybadger.Filter.Default
|
||||
|
||||
# Console backend configuration
|
||||
config :logger, :console,
|
||||
format: "[$level] $message\n",
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ defmodule Towerops.ErrorTrackerIgnorer do
|
|||
Drops benign errors from ErrorTracker that occur during K8s pod shutdown
|
||||
or transient DB connection cycling.
|
||||
|
||||
Matches the same patterns as `Towerops.HoneybadgerFilter` and
|
||||
`Towerops.LoggerFilters.drop_shutdown_errors/2`.
|
||||
Matches the same patterns as `Towerops.LoggerFilters.drop_shutdown_errors/2`.
|
||||
"""
|
||||
|
||||
@behaviour ErrorTracker.Ignorer
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
defmodule Towerops.HoneybadgerFilter do
|
||||
@moduledoc """
|
||||
Custom Honeybadger filter to exclude noisy errors during deployments
|
||||
and filter sensitive SNMP credentials from error reports.
|
||||
|
||||
Filters out errors that occur during normal application shutdown/restart:
|
||||
- OTP supervisor/gen_server termination errors
|
||||
- Redix connection errors (tcp_closed, econnrefused)
|
||||
- DBConnection errors during shutdown
|
||||
- Normal process exits (:normal, :shutdown, {:shutdown, _})
|
||||
|
||||
Also filters sensitive data from error reports (passwords, tokens, SNMP communities).
|
||||
"""
|
||||
|
||||
use Honeybadger.Filter.Mixin
|
||||
|
||||
# Named supervisors/processes that generate errors during normal shutdown
|
||||
@ignored_registered_names [
|
||||
"memsup",
|
||||
"cpu_sup",
|
||||
"Elixir.PollerSupervisor"
|
||||
]
|
||||
|
||||
# Sensitive keys that should be filtered from error reports
|
||||
@sensitive_keys [
|
||||
"password",
|
||||
"snmp_community",
|
||||
"community",
|
||||
"secret",
|
||||
"token",
|
||||
"api_key",
|
||||
"auth_password",
|
||||
"priv_password",
|
||||
"auth_pass",
|
||||
"priv_pass"
|
||||
]
|
||||
|
||||
@impl Honeybadger.Filter
|
||||
def filter_context(context) do
|
||||
cond do
|
||||
# Named process in our ignore list
|
||||
get_in(context, [:registered_name]) in @ignored_registered_names ->
|
||||
nil
|
||||
|
||||
# Anonymous gen_server shutdown errors (OTP domain, gen_server error_info)
|
||||
otp_gen_server_shutdown?(context) ->
|
||||
nil
|
||||
|
||||
# Redix connection errors during shutdown/restart
|
||||
redix_connection_error?(context) ->
|
||||
nil
|
||||
|
||||
# DBConnection errors during shutdown/restart
|
||||
db_connection_error?(context) ->
|
||||
nil
|
||||
|
||||
# Normal process shutdown (exit :normal, :shutdown, {:shutdown, _})
|
||||
normal_shutdown?(context) ->
|
||||
nil
|
||||
|
||||
true ->
|
||||
context
|
||||
|> super()
|
||||
|> filter_sensitive_data()
|
||||
end
|
||||
end
|
||||
|
||||
@impl Honeybadger.Filter
|
||||
def filter_params(params) when is_map(params) do
|
||||
Map.new(params, fn {key, value} ->
|
||||
if should_filter?(key) do
|
||||
{key, "[FILTERED]"}
|
||||
else
|
||||
{key, filter_params(value)}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def filter_params(params), do: params
|
||||
|
||||
# Detect anonymous gen_server termination during shutdown
|
||||
defp otp_gen_server_shutdown?(context) do
|
||||
domain = get_in(context, [:domain])
|
||||
mfa = get_in(context, [:mfa])
|
||||
|
||||
domain == ["otp"] and match?(["gen_server", "error_info", _], mfa)
|
||||
end
|
||||
|
||||
# Detect Redix connection errors during shutdown/restart
|
||||
defp redix_connection_error?(context) do
|
||||
reason = get_in(context, [:reason])
|
||||
message = get_in(context, [:message])
|
||||
|
||||
cond do
|
||||
# Redix connection closed errors
|
||||
is_tuple(reason) and elem(reason, 0) == :tcp_closed ->
|
||||
true
|
||||
|
||||
# Redix connection refused (during restart)
|
||||
is_tuple(reason) and elem(reason, 0) == :econnrefused ->
|
||||
true
|
||||
|
||||
# String message check for Redix errors
|
||||
is_binary(message) and String.contains?(message, "Redix") ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Detect DBConnection errors during shutdown/restart
|
||||
defp db_connection_error?(context) do
|
||||
reason = get_in(context, [:reason])
|
||||
message = get_in(context, [:message])
|
||||
|
||||
cond do
|
||||
# DBConnection.ConnectionError
|
||||
is_exception(reason) and reason.__struct__ == DBConnection.ConnectionError ->
|
||||
true
|
||||
|
||||
# String message check
|
||||
is_binary(message) and String.contains?(message, "DBConnection") ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Detect normal process shutdown
|
||||
defp normal_shutdown?(context) do
|
||||
reason = get_in(context, [:reason])
|
||||
|
||||
case reason do
|
||||
:normal -> true
|
||||
:shutdown -> true
|
||||
{:shutdown, _} -> true
|
||||
{:port_died, :normal} -> true
|
||||
{:port_died, :shutdown} -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
# Filter sensitive data from context
|
||||
defp filter_sensitive_data(nil), do: nil
|
||||
|
||||
defp filter_sensitive_data(context) when is_map(context) do
|
||||
context
|
||||
|> Map.update(:cgi_data, %{}, &filter_params/1)
|
||||
|> Map.update(:params, %{}, &filter_params/1)
|
||||
|> Map.update(:session, %{}, &filter_params/1)
|
||||
|> Map.update(:context, %{}, &filter_params/1)
|
||||
end
|
||||
|
||||
defp filter_sensitive_data(context), do: context
|
||||
|
||||
# Check if a key should be filtered
|
||||
defp should_filter?(key) when is_binary(key) do
|
||||
key_lower = String.downcase(key)
|
||||
Enum.any?(@sensitive_keys, &String.contains?(key_lower, &1))
|
||||
end
|
||||
|
||||
defp should_filter?(key) when is_atom(key) do
|
||||
should_filter?(Atom.to_string(key))
|
||||
end
|
||||
|
||||
defp should_filter?(_), do: false
|
||||
end
|
||||
|
|
@ -49,10 +49,6 @@ defmodule ToweropsWeb.Endpoint do
|
|||
from: Application.compile_env(:towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}),
|
||||
gzip: false
|
||||
|
||||
if Mix.env() == :dev do
|
||||
plug Tidewave
|
||||
end
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ defmodule ToweropsWeb.Router do
|
|||
@moduledoc false
|
||||
|
||||
use ToweropsWeb, :router
|
||||
use Honeybadger.Plug
|
||||
use ErrorTracker.Web, :router
|
||||
|
||||
import Oban.Web.Router
|
||||
|
|
|
|||
6
mix.exs
6
mix.exs
|
|
@ -96,8 +96,8 @@ defmodule Towerops.MixProject do
|
|||
github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", app: false, compile: false, depth: 1},
|
||||
{:swoosh, "~> 1.24"},
|
||||
{:gen_smtp, "~> 1.0"},
|
||||
{:cbor, "~> 1.0"},
|
||||
{:req, "~> 0.5"},
|
||||
{:gaiia, path: "vendor/gaiia"},
|
||||
{:castore, "~> 1.0"},
|
||||
{:sweet_xml, "~> 0.7"},
|
||||
{:yaml_elixir, "~> 2.9"},
|
||||
|
|
@ -120,7 +120,6 @@ defmodule Towerops.MixProject do
|
|||
{:absinthe, "~> 1.7"},
|
||||
{:absinthe_plug, "~> 1.5"},
|
||||
{:absinthe_phoenix, "~> 2.0"},
|
||||
{:honeybadger, "~> 0.24"},
|
||||
{:error_tracker, "~> 0.7"},
|
||||
{:error_tracker_notifier, "~> 0.2"},
|
||||
{:stream_data, "~> 1.1", only: :test},
|
||||
|
|
@ -134,8 +133,7 @@ defmodule Towerops.MixProject do
|
|||
{:cloak_ecto, "~> 1.3"},
|
||||
{:geo_postgis, "~> 3.7"},
|
||||
{:logger_file_backend, "~> 0.0.13", only: :dev},
|
||||
{:logger_backends, "~> 1.0", only: :dev},
|
||||
{:tidewave, "~> 0.5", only: :dev}
|
||||
{:logger_backends, "~> 1.0", only: :dev}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
5
mix.lock
5
mix.lock
|
|
@ -6,10 +6,8 @@
|
|||
"bandit": {:hex, :bandit, "1.11.0", "dbdd9c9963f146ee9da9860d1ee5b0ffd65cea51fe2aab3f3273df84329d133a", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "c949d93a325a28da2333dde5a9ab61986ad2c2b7226347db6a28303b9139865e"},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"castore": {:hex, :castore, "1.0.18", "5e43ef0ec7d31195dfa5a65a86e6131db999d074179d2ba5a8de11fe14570f55", [:mix], [], "hexpm", "f393e4fe6317829b158fb74d86eb681f737d2fe326aa61ccf6293c4104957e34"},
|
||||
"cbor": {:hex, :cbor, "1.0.2", "9b0af85af291a556e10a0ffd48ba9a21a75e711828fafd3af193d56d95f0907f", [:mix], [], "hexpm", "edbc9b4a16eb93a582437b9b249c340a75af03958e338fb43d8c1be9fc65b864"},
|
||||
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
|
||||
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
|
||||
"circular_buffer": {:hex, :circular_buffer, "1.0.0", "25c004da0cba7bd8bc1bdabded4f9a902d095e20600fd15faf1f2ffbaea18a07", [:mix], [], "hexpm", "c829ec31c13c7bafd1f546677263dff5bfb006e929f25635878ac3cfba8749e5"},
|
||||
"cloak": {:hex, :cloak, "1.1.4", "aba387b22ea4d80d92d38ab1890cc528b06e0e7ef2a4581d71c3fdad59e997e7", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "92b20527b9aba3d939fab0dd32ce592ff86361547cfdc87d74edce6f980eb3d7"},
|
||||
"cloak_ecto": {:hex, :cloak_ecto, "1.3.0", "0de127c857d7452ba3c3367f53fb814b0410ff9c680a8d20fbe8b9a3c57a1118", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "314beb0c123b8a800418ca1d51065b27ba3b15f085977e65c0f7b2adab2de1cc"},
|
||||
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
|
||||
|
|
@ -40,7 +38,6 @@
|
|||
"gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
|
||||
"hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"},
|
||||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
|
||||
"honeybadger": {:hex, :honeybadger, "0.27.0", "970efc796b5fb6db5c6a6e8e68660865b249bd184c306cfa323797a4b3570be9", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: true]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:hackney, "~> 1.8 or ~> 4.0", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.0.0 and < 2.0.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:process_tree, "~> 0.2.1", [hex: :process_tree, repo: "hexpm", optional: false]}, {:req, "~> 0.5.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "236612eeac103619c6d4297090f6a3bf517c0f5ad5f210fec7ba8a90d47a8209"},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
"httpoison": {:hex, :httpoison, "2.3.0", "10eef046405bc44ba77dc5b48957944df8952cc4966364b3cf6aa71dce6de587", [:mix], [{:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d388ee70be56d31a901e333dbcdab3682d356f651f93cf492ba9f06056436a2c"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
|
|
@ -80,7 +77,6 @@
|
|||
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||
"postgrex": {:hex, :postgrex, "0.22.1", "b3665ad17e15441557da8f45eeebfcd56e4a2b0b98538b855679a13d05e5cc5d", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "df59f828b167b49a5853f645b65f57eb1bc5f3b230497ceaca7af5d8ac05afef"},
|
||||
"process_tree": {:hex, :process_tree, "0.2.1", "4ebcaa96c64a7833467909f49fee28a8e62eed04975613f4c81b4b99424f7e8a", [:mix], [], "hexpm", "68eee6bf0514351aeeda7037f1a6003c0e25de48fe6b7d15a1b0aebb4b35e713"},
|
||||
"prom_ex": {:hex, :prom_ex, "1.11.0", "1f6d67f2dead92224cb4f59beb3e4d319257c5728d9638b4a5e8ceb51a4f9c7e", [:mix], [{:absinthe, ">= 1.7.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.11.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.18", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.10.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.4", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:peep, "~> 3.0", [hex: :peep, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.20.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.16.0", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 2.6.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.1", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "76b074bc3730f0802978a7eb5c7091a65473eaaf07e99ec9e933138dcc327805"},
|
||||
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
|
||||
"redix": {:hex, :redix, "1.5.3", "4eaae29c75e3285c0ff9957046b7c209aa7f72a023a17f0a9ea51c2a50ab5b0f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7b06fb5246373af41f5826b03334dfa3f636347d4d5d98b4d455b699d425ae7e"},
|
||||
|
|
@ -97,7 +93,6 @@
|
|||
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
|
||||
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
|
||||
"tidewave": {:hex, :tidewave, "0.5.6", "91f35540b5599640443f1d3a1c6166bf506e202840261a6344e384e8813c1f64", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "dc82d52b8b6ffc04680544b17cd340c7d4166bb0d63999eb960850526866b533"},
|
||||
"tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
defmodule Towerops.HoneybadgerFilterTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Towerops.HoneybadgerFilter
|
||||
|
||||
describe "filter_params/1" do
|
||||
test "filters SNMP community strings" do
|
||||
params = %{
|
||||
"name" => "Device 1",
|
||||
"snmp_community" => "public",
|
||||
"ip_address" => "192.168.1.1"
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["name"] == "Device 1"
|
||||
assert filtered["snmp_community"] == "[FILTERED]"
|
||||
assert filtered["ip_address"] == "192.168.1.1"
|
||||
end
|
||||
|
||||
test "filters community strings with just 'community' key" do
|
||||
params = %{"community" => "secret123", "device_id" => "abc"}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["community"] == "[FILTERED]"
|
||||
assert filtered["device_id"] == "abc"
|
||||
end
|
||||
|
||||
test "filters passwords" do
|
||||
params = %{"email" => "user@example.com", "password" => "secret"}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["email"] == "user@example.com"
|
||||
assert filtered["password"] == "[FILTERED]"
|
||||
end
|
||||
|
||||
test "filters SNMPv3 credentials" do
|
||||
params = %{
|
||||
"snmp_version" => "3",
|
||||
"auth_password" => "authpass",
|
||||
"priv_password" => "privpass",
|
||||
"username" => "admin"
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["snmp_version"] == "3"
|
||||
assert filtered["auth_password"] == "[FILTERED]"
|
||||
assert filtered["priv_password"] == "[FILTERED]"
|
||||
assert filtered["username"] == "admin"
|
||||
end
|
||||
|
||||
test "filters API keys and tokens" do
|
||||
params = %{
|
||||
"api_key" => "key123",
|
||||
"token" => "token456",
|
||||
"secret" => "secret789"
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["api_key"] == "[FILTERED]"
|
||||
assert filtered["token"] == "[FILTERED]"
|
||||
assert filtered["secret"] == "[FILTERED]"
|
||||
end
|
||||
|
||||
test "filters nested params" do
|
||||
params = %{
|
||||
"device" => %{
|
||||
"name" => "Router",
|
||||
"snmp_community" => "private",
|
||||
"ip_address" => "10.0.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["device"]["name"] == "Router"
|
||||
assert filtered["device"]["snmp_community"] == "[FILTERED]"
|
||||
assert filtered["device"]["ip_address"] == "10.0.0.1"
|
||||
end
|
||||
|
||||
test "filters atom keys" do
|
||||
params = %{
|
||||
name: "Device",
|
||||
snmp_community: "public",
|
||||
ip_address: "192.168.1.1"
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered[:name] == "Device"
|
||||
assert filtered[:snmp_community] == "[FILTERED]"
|
||||
assert filtered[:ip_address] == "192.168.1.1"
|
||||
end
|
||||
|
||||
test "handles case-insensitive filtering" do
|
||||
params = %{
|
||||
"SNMP_COMMUNITY" => "public",
|
||||
"SnmpCommunity" => "private",
|
||||
"API_KEY" => "secret"
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered["SNMP_COMMUNITY"] == "[FILTERED]"
|
||||
assert filtered["SnmpCommunity"] == "[FILTERED]"
|
||||
assert filtered["API_KEY"] == "[FILTERED]"
|
||||
end
|
||||
|
||||
test "does not filter non-sensitive data" do
|
||||
params = %{
|
||||
"name" => "Device",
|
||||
"ip_address" => "192.168.1.1",
|
||||
"description" => "Main router"
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_params(params)
|
||||
|
||||
assert filtered == params
|
||||
end
|
||||
end
|
||||
|
||||
describe "filter_context/1" do
|
||||
test "filters sensitive data from context params" do
|
||||
context = %{
|
||||
params: %{
|
||||
"device" => %{"snmp_community" => "public", "name" => "Router"}
|
||||
}
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered.params["device"]["name"] == "Router"
|
||||
assert filtered.params["device"]["snmp_community"] == "[FILTERED]"
|
||||
end
|
||||
|
||||
test "filters sensitive data from cgi_data" do
|
||||
context = %{
|
||||
cgi_data: %{"auth_token" => "Bearer token123", "user_agent" => "Mozilla/5.0"}
|
||||
}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered.cgi_data["auth_token"] == "[FILTERED]"
|
||||
assert filtered.cgi_data["user_agent"] == "Mozilla/5.0"
|
||||
end
|
||||
|
||||
test "filters ignored registered names" do
|
||||
context = %{registered_name: "memsup"}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters Redix connection errors (tcp_closed)" do
|
||||
context = %{reason: {:tcp_closed, :some_data}}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters Redix connection errors (econnrefused)" do
|
||||
context = %{reason: {:econnrefused, :some_data}}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters Redix errors by message" do
|
||||
context = %{message: "Redix connection failed"}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters DBConnection errors by exception" do
|
||||
context = %{reason: %DBConnection.ConnectionError{message: "connection lost"}}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters DBConnection errors by message" do
|
||||
context = %{message: "DBConnection timeout"}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters normal shutdown" do
|
||||
context = %{reason: :normal}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters shutdown" do
|
||||
context = %{reason: :shutdown}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters shutdown with reason" do
|
||||
context = %{reason: {:shutdown, :application_stopped}}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters port_died with :normal reason" do
|
||||
context = %{reason: {:port_died, :normal}}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
|
||||
test "filters port_died with :shutdown reason" do
|
||||
context = %{reason: {:port_died, :shutdown}}
|
||||
|
||||
filtered = HoneybadgerFilter.filter_context(context)
|
||||
|
||||
assert filtered == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
34
vendor/gaiia/lib/gaiia.ex
vendored
Normal file
34
vendor/gaiia/lib/gaiia.ex
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
defmodule Gaiia do
|
||||
@moduledoc """
|
||||
Elixir client for the [Gaiia](https://gaiia.com) GraphQL API.
|
||||
|
||||
The top-level `Gaiia` module is a thin convenience wrapper that
|
||||
builds an implicit client from application config:
|
||||
|
||||
config :gaiia,
|
||||
endpoint: "https://api.gaiia.com/graphql",
|
||||
token: System.get_env("GAIIA_TOKEN")
|
||||
|
||||
Gaiia.query("query { account(id: $id) { id name } }", %{"id" => "..."})
|
||||
|
||||
For per-call control over headers, request options, or to use multiple
|
||||
endpoints simultaneously, build a `Gaiia.Client` explicitly:
|
||||
|
||||
client = Gaiia.Client.new(token: "...", headers: [{"x-trace-id", "abc"}])
|
||||
Gaiia.Client.query(client, "{ __typename }")
|
||||
"""
|
||||
|
||||
alias Gaiia.Client
|
||||
|
||||
@doc "Run a GraphQL query using the default client built from app config."
|
||||
@spec query(String.t(), map(), keyword()) :: {:ok, map()} | {:error, Gaiia.Error.t()}
|
||||
def query(query, variables \\ %{}, opts \\ []), do: Client.query(default_client(), query, variables, opts)
|
||||
|
||||
@doc "Run a GraphQL mutation using the default client built from app config."
|
||||
@spec mutate(String.t(), map(), keyword()) :: {:ok, map()} | {:error, Gaiia.Error.t()}
|
||||
def mutate(mutation, variables \\ %{}, opts \\ []), do: Client.mutate(default_client(), mutation, variables, opts)
|
||||
|
||||
@doc "Build a default client from application config."
|
||||
@spec default_client() :: Client.t()
|
||||
def default_client, do: Client.new()
|
||||
end
|
||||
142
vendor/gaiia/lib/gaiia/client.ex
vendored
Normal file
142
vendor/gaiia/lib/gaiia/client.ex
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
defmodule Gaiia.Client do
|
||||
@moduledoc """
|
||||
HTTP client for the Gaiia GraphQL API.
|
||||
|
||||
A `Gaiia.Client` is a lightweight, immutable struct holding the endpoint,
|
||||
optional bearer token, custom headers, and pass-through options forwarded
|
||||
to `Req`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Defaults can be supplied through application config:
|
||||
|
||||
config :gaiia,
|
||||
endpoint: "https://api.gaiia.com/graphql",
|
||||
token: System.get_env("GAIIA_TOKEN")
|
||||
|
||||
## Example
|
||||
|
||||
client = Gaiia.Client.new()
|
||||
|
||||
{:ok, %{"account" => account}} =
|
||||
Gaiia.Client.query(client, ~S\"\"\"
|
||||
query($id: GlobalID!) {
|
||||
account(id: $id) { id name }
|
||||
}
|
||||
\"\"\", %{"id" => "account_8rnXNuR5sKP5uNwoPL41Zp"})
|
||||
"""
|
||||
|
||||
alias Gaiia.Error
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
endpoint: String.t(),
|
||||
token: String.t() | nil,
|
||||
headers: [{String.t(), String.t()}],
|
||||
req_options: keyword()
|
||||
}
|
||||
|
||||
@enforce_keys [:endpoint]
|
||||
defstruct endpoint: nil, token: nil, headers: [], req_options: []
|
||||
|
||||
@doc """
|
||||
Build a new client.
|
||||
|
||||
## Options
|
||||
|
||||
* `:endpoint` — Base GraphQL endpoint URL. Required unless set in app env.
|
||||
* `:token` — Bearer token sent in the `Authorization` header.
|
||||
* `:headers` — Extra headers as `[{name, value}]`.
|
||||
* `:req_options` — Keyword list of options forwarded to `Req.request/1`.
|
||||
Useful for testing (`:adapter`, `:plug`) and tuning
|
||||
(`:retry`, `:receive_timeout`, etc.).
|
||||
|
||||
Raises `ArgumentError` when no endpoint can be resolved.
|
||||
"""
|
||||
@spec new(keyword()) :: t()
|
||||
def new(opts \\ []) do
|
||||
endpoint = fetch_required(opts, :endpoint)
|
||||
|
||||
%__MODULE__{
|
||||
endpoint: endpoint,
|
||||
token: Keyword.get(opts, :token, Application.get_env(:gaiia, :token)),
|
||||
headers: Keyword.get(opts, :headers, []),
|
||||
req_options: Keyword.get(opts, :req_options, Application.get_env(:gaiia, :req_options, []))
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Run a GraphQL query.
|
||||
|
||||
Returns `{:ok, data}` on success, where `data` is the value of the
|
||||
`data` field in the GraphQL response. Returns `{:error, %Gaiia.Error{}}`
|
||||
on any failure mode.
|
||||
|
||||
## Options
|
||||
|
||||
* `:operation_name` — GraphQL operation name to send (`operationName`).
|
||||
"""
|
||||
@spec query(t(), String.t(), map(), keyword()) :: {:ok, map()} | {:error, Error.t()}
|
||||
def query(%__MODULE__{} = client, query, variables \\ %{}, opts \\ []) do
|
||||
body = build_body(query, variables, opts)
|
||||
|
||||
client
|
||||
|> build_request(body)
|
||||
|> Req.request()
|
||||
|> handle_response()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Run a GraphQL mutation. Mechanically identical to `query/4` — provided
|
||||
for readability at call sites.
|
||||
"""
|
||||
@spec mutate(t(), String.t(), map(), keyword()) :: {:ok, map()} | {:error, Error.t()}
|
||||
def mutate(client, mutation, variables \\ %{}, opts \\ []), do: query(client, mutation, variables, opts)
|
||||
|
||||
## ---- request construction ----
|
||||
|
||||
@spec build_body(String.t(), map(), keyword()) :: map()
|
||||
defp build_body(query, variables, opts) do
|
||||
put_operation_name(%{"query" => query, "variables" => variables}, Keyword.get(opts, :operation_name))
|
||||
end
|
||||
|
||||
defp put_operation_name(body, nil), do: body
|
||||
defp put_operation_name(body, name), do: Map.put(body, "operationName", name)
|
||||
|
||||
@spec build_request(t(), map()) :: Req.Request.t()
|
||||
defp build_request(%__MODULE__{} = client, body) do
|
||||
[
|
||||
method: :post,
|
||||
url: client.endpoint,
|
||||
json: body,
|
||||
headers: headers_for(client)
|
||||
]
|
||||
|> Keyword.merge(client.req_options)
|
||||
|> Req.new()
|
||||
end
|
||||
|
||||
defp headers_for(%__MODULE__{token: nil, headers: extra}), do: extra
|
||||
defp headers_for(%__MODULE__{token: token, headers: extra}), do: [{"authorization", "Bearer " <> token} | extra]
|
||||
|
||||
## ---- response handling ----
|
||||
|
||||
@spec handle_response({:ok, Req.Response.t()} | {:error, Exception.t()}) ::
|
||||
{:ok, map()} | {:error, Error.t()}
|
||||
defp handle_response({:ok, %Req.Response{status: status, body: body}}) when status in 200..299, do: decode_graphql(body)
|
||||
|
||||
defp handle_response({:ok, %Req.Response{status: status, body: body}}), do: {:error, Error.http(status, body)}
|
||||
|
||||
defp handle_response({:error, exception}), do: {:error, Error.network(exception)}
|
||||
|
||||
defp decode_graphql(%{"errors" => [_ | _] = errors}), do: {:error, Error.graphql(errors)}
|
||||
defp decode_graphql(%{"data" => data}), do: {:ok, data}
|
||||
defp decode_graphql(other), do: {:error, Error.decode(other)}
|
||||
|
||||
## ---- option resolution ----
|
||||
|
||||
defp fetch_required(opts, key) do
|
||||
case Keyword.get(opts, key, Application.get_env(:gaiia, key)) do
|
||||
nil -> raise ArgumentError, "missing required Gaiia.Client option: #{inspect(key)}"
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
81
vendor/gaiia/lib/gaiia/error.ex
vendored
Normal file
81
vendor/gaiia/lib/gaiia/error.ex
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
defmodule Gaiia.Error do
|
||||
@moduledoc """
|
||||
Structured error returned by `Gaiia.Client` calls.
|
||||
|
||||
The `:kind` discriminates the failure mode so callers can pattern match
|
||||
rather than parse messages:
|
||||
|
||||
* `:graphql` — the server returned a 200 with one or more entries in `errors`
|
||||
* `:http` — the server returned a non-2xx HTTP response
|
||||
* `:network` — the request never reached the server (connection refused, DNS, etc.)
|
||||
* `:decode` — the response was 2xx but the body was not a valid GraphQL envelope
|
||||
"""
|
||||
|
||||
@type kind :: :graphql | :http | :network | :decode
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
kind: kind(),
|
||||
message: String.t(),
|
||||
status: pos_integer() | nil,
|
||||
errors: [map()] | nil,
|
||||
details: term()
|
||||
}
|
||||
|
||||
@enforce_keys [:kind, :message]
|
||||
defexception [:kind, :message, :status, :errors, :details]
|
||||
|
||||
@doc "Build a `:graphql` error from a list of GraphQL error maps."
|
||||
@spec graphql([map()]) :: t()
|
||||
def graphql(errors) when is_list(errors) do
|
||||
%__MODULE__{
|
||||
kind: :graphql,
|
||||
message: "GraphQL error: " <> format_graphql_messages(errors),
|
||||
errors: errors
|
||||
}
|
||||
end
|
||||
|
||||
@doc "Build an `:http` error from a status code and response body."
|
||||
@spec http(pos_integer(), term()) :: t()
|
||||
def http(status, body) when is_integer(status) do
|
||||
%__MODULE__{
|
||||
kind: :http,
|
||||
message: "HTTP #{status} from Gaiia API",
|
||||
status: status,
|
||||
details: body
|
||||
}
|
||||
end
|
||||
|
||||
@doc "Build a `:network` error from a transport-layer exception."
|
||||
@spec network(Exception.t() | term()) :: t()
|
||||
def network(%{__exception__: true} = exception) do
|
||||
%__MODULE__{
|
||||
kind: :network,
|
||||
message: "Network error: " <> Exception.message(exception),
|
||||
details: exception
|
||||
}
|
||||
end
|
||||
|
||||
def network(other) do
|
||||
%__MODULE__{
|
||||
kind: :network,
|
||||
message: "Network error: " <> inspect(other),
|
||||
details: other
|
||||
}
|
||||
end
|
||||
|
||||
@doc "Build a `:decode` error from an undecodable response body."
|
||||
@spec decode(term()) :: t()
|
||||
def decode(body) do
|
||||
%__MODULE__{
|
||||
kind: :decode,
|
||||
message: "Could not decode Gaiia response body",
|
||||
details: body
|
||||
}
|
||||
end
|
||||
|
||||
defp format_graphql_messages([]), do: "<no message>"
|
||||
|
||||
defp format_graphql_messages(errors) do
|
||||
Enum.map_join(errors, "; ", &Map.get(&1, "message", "<no message>"))
|
||||
end
|
||||
end
|
||||
160
vendor/gaiia/lib/gaiia/global_id.ex
vendored
Normal file
160
vendor/gaiia/lib/gaiia/global_id.ex
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
defmodule Gaiia.GlobalID do
|
||||
@moduledoc """
|
||||
Encode and decode Gaiia Global IDs.
|
||||
|
||||
Global IDs combine a snake_cased type name with a Base58-encoded UUID,
|
||||
separated by an underscore: `account_8rnXNuR5sKP5uNwoPL41Zp`.
|
||||
"""
|
||||
|
||||
@alphabet "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
|
||||
@base 58
|
||||
@target_len 22
|
||||
|
||||
@doc """
|
||||
Encodes a type name and UUID into a Global ID.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Gaiia.GlobalID.encode("Account", "3c3b1978-6a68-4a13-bdc2-2d51c8ef7519")
|
||||
"account_8rnXNuR5sKP5uNwoPL41Zp"
|
||||
|
||||
"""
|
||||
@spec encode(String.t(), String.t()) :: String.t()
|
||||
def encode(type_name, uuid_str) do
|
||||
snake = snake_case(type_name)
|
||||
short = short_uuid(uuid_str)
|
||||
"#{snake}_#{short}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a Global ID into a tuple of `{type, uuid}`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Gaiia.GlobalID.decode("account_8rnXNuR5sKP5uNwoPL41Zp")
|
||||
{"account", "3c3b1978-6a68-4a13-bdc2-2d51c8ef7519"}
|
||||
|
||||
"""
|
||||
@spec decode(String.t()) :: {String.t(), String.t()}
|
||||
def decode(global_id) do
|
||||
short_id = String.slice(global_id, -@target_len, @target_len)
|
||||
type = String.slice(global_id, 0, byte_size(global_id) - @target_len - 1)
|
||||
{type, short_to_uuid(short_id)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extracts the type name from a Global ID.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Gaiia.GlobalID.type("account_8rnXNuR5sKP5uNwoPL41Zp")
|
||||
"account"
|
||||
|
||||
"""
|
||||
@spec type(String.t()) :: String.t()
|
||||
def type(global_id) do
|
||||
type_len = byte_size(global_id) - @target_len - 1
|
||||
String.slice(global_id, 0, type_len)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extracts the UUID from a Global ID.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Gaiia.GlobalID.to_uuid("account_8rnXNuR5sKP5uNwoPL41Zp")
|
||||
"3c3b1978-6a68-4a13-bdc2-2d51c8ef7519"
|
||||
|
||||
"""
|
||||
@spec to_uuid(String.t()) :: String.t()
|
||||
def to_uuid(global_id) do
|
||||
{_type, uuid} = decode(global_id)
|
||||
uuid
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a type name and UUID to a Global ID. Alias for `encode/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Gaiia.GlobalID.from_uuid("Account", "3c3b1978-6a68-4a13-bdc2-2d51c8ef7519")
|
||||
"account_8rnXNuR5sKP5uNwoPL41Zp"
|
||||
|
||||
"""
|
||||
@spec from_uuid(String.t(), String.t()) :: String.t()
|
||||
def from_uuid(type_name, uuid_str), do: encode(type_name, uuid_str)
|
||||
|
||||
@doc """
|
||||
Encodes a UUID to a 22-character Base58 short UUID.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Gaiia.GlobalID.short_uuid("3c3b1978-6a68-4a13-bdc2-2d51c8ef7519")
|
||||
"8rnXNuR5sKP5uNwoPL41Zp"
|
||||
|
||||
"""
|
||||
@spec short_uuid(String.t()) :: String.t()
|
||||
def short_uuid(uuid_str) do
|
||||
stripped = String.downcase(String.replace(uuid_str, "-", ""))
|
||||
num = String.to_integer(stripped, 16)
|
||||
encode_base58(num)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a short UUID back to a standard UUID string.
|
||||
"""
|
||||
@spec short_to_uuid(String.t()) :: String.t()
|
||||
def short_to_uuid(short_id) do
|
||||
num = decode_base58(short_id)
|
||||
hex = num |> Integer.to_string(16) |> String.pad_leading(32, "0") |> String.downcase()
|
||||
format_uuid(hex)
|
||||
end
|
||||
|
||||
# -- Private: encode --
|
||||
|
||||
defp encode_base58(0), do: String.duplicate(<<:binary.at(@alphabet, 0)>>, @target_len)
|
||||
|
||||
defp encode_base58(num) do
|
||||
chars = do_encode(num, [])
|
||||
first = <<:binary.at(@alphabet, 0)>>
|
||||
Enum.join(pad_list(chars, @target_len, first))
|
||||
end
|
||||
|
||||
defp do_encode(0, acc), do: acc
|
||||
|
||||
defp do_encode(n, acc) do
|
||||
idx = rem(n, @base)
|
||||
char = <<:binary.at(@alphabet, idx)>>
|
||||
do_encode(div(n, @base), [char | acc])
|
||||
end
|
||||
|
||||
# -- Private: decode --
|
||||
|
||||
defp decode_base58(short_id) do
|
||||
short_id
|
||||
|> String.to_charlist()
|
||||
|> Enum.reduce(0, fn char, acc -> acc * @base + alpha_index(char) end)
|
||||
end
|
||||
|
||||
@alphabet_index_map for {ch, i} <- Enum.with_index(String.to_charlist(@alphabet)), into: %{}, do: {ch, i}
|
||||
|
||||
defp alpha_index(char), do: Map.fetch!(@alphabet_index_map, char)
|
||||
|
||||
# -- Private: helpers --
|
||||
|
||||
defp pad_list(chars, target_len, pad_char) do
|
||||
if length(chars) < target_len do
|
||||
List.duplicate(pad_char, target_len - length(chars)) ++ chars
|
||||
else
|
||||
chars
|
||||
end
|
||||
end
|
||||
|
||||
defp format_uuid(hex) do
|
||||
"#{String.slice(hex, 0, 8)}-#{String.slice(hex, 8, 4)}-#{String.slice(hex, 12, 4)}-#{String.slice(hex, 16, 4)}-#{String.slice(hex, 20, 12)}"
|
||||
end
|
||||
|
||||
defp snake_case(type_name) do
|
||||
type_name |> String.replace(~r/([a-z])([A-Z])/, "\\1_\\2") |> String.downcase()
|
||||
end
|
||||
end
|
||||
35
vendor/gaiia/lib/gaiia/mutations.ex
vendored
Normal file
35
vendor/gaiia/lib/gaiia/mutations.ex
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
defmodule Gaiia.Mutations do
|
||||
@moduledoc """
|
||||
Auto-generated functions for every mutation operation in the Gaiia
|
||||
GraphQL API.
|
||||
|
||||
Signature, semantics, and return-value handling are identical to
|
||||
`Gaiia.Queries` — see that module for details.
|
||||
"""
|
||||
|
||||
alias Gaiia.Client
|
||||
alias Gaiia.Operation
|
||||
alias Gaiia.Schema
|
||||
|
||||
for op <- Schema.mutations() do
|
||||
@name op["name"]
|
||||
@fun_name Schema.function_name(@name)
|
||||
@args op["args"]
|
||||
@description op["description"] || "GraphQL mutation `#{@name}`."
|
||||
|
||||
@doc """
|
||||
#{@description}
|
||||
|
||||
GraphQL: `mutation { #{@name} }`
|
||||
"""
|
||||
@spec unquote(@fun_name)(Client.t(), map(), String.t(), keyword()) ::
|
||||
{:ok, term()} | {:error, Gaiia.Error.t()}
|
||||
def unquote(@fun_name)(client, variables \\ %{}, selection \\ "", opts \\ []) do
|
||||
operation = Operation.build(:mutation, unquote(@name), unquote(Macro.escape(@args)), variables, selection)
|
||||
|
||||
with {:ok, data} <- Client.mutate(client, operation, variables, opts) do
|
||||
{:ok, Map.get(data, unquote(@name))}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
55
vendor/gaiia/lib/gaiia/operation.ex
vendored
Normal file
55
vendor/gaiia/lib/gaiia/operation.ex
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
defmodule Gaiia.Operation do
|
||||
@moduledoc """
|
||||
Build GraphQL operation strings from introspection-style argument specs
|
||||
at runtime.
|
||||
|
||||
Operations have shape:
|
||||
|
||||
<op_type> <op_name>($a: T!, $b: U) { <op_name>(a: $a, b: $b) { <selection> } }
|
||||
|
||||
Only variables that the caller actually supplies are declared and passed,
|
||||
so GraphQL doesn't reject the request for unused variables.
|
||||
"""
|
||||
|
||||
alias Gaiia.TypeRef
|
||||
|
||||
@type op_type :: :query | :mutation
|
||||
@type arg_spec :: %{required(String.t()) => term()}
|
||||
|
||||
@doc """
|
||||
Build a GraphQL operation string.
|
||||
|
||||
* `op_type` — `:query` or `:mutation`
|
||||
* `name` — the operation/field name (e.g. `"account"`)
|
||||
* `args` — list of argument specs `[%{"name" => "id", "type" => type_ref}, ...]`
|
||||
* `variables` — variables map; only keys present here are declared/passed
|
||||
* `selection` — selection set body (without braces). Empty string means no selection.
|
||||
"""
|
||||
@spec build(op_type(), String.t(), [arg_spec()], map(), String.t()) :: String.t()
|
||||
def build(op_type, name, args, variables, selection) do
|
||||
used = filter_used_args(args, variables)
|
||||
"#{keyword(op_type)} #{name}#{declarations(used)} { #{name}#{call_args(used)}#{selection_block(selection)} }"
|
||||
end
|
||||
|
||||
defp keyword(:query), do: "query"
|
||||
defp keyword(:mutation), do: "mutation"
|
||||
|
||||
defp filter_used_args(args, variables) do
|
||||
Enum.filter(args, fn %{"name" => name} -> Map.has_key?(variables, name) end)
|
||||
end
|
||||
|
||||
defp declarations([]), do: ""
|
||||
|
||||
defp declarations(args) do
|
||||
"(" <> Enum.map_join(args, ", ", fn %{"name" => n, "type" => t} -> "$#{n}: #{TypeRef.render(t)}" end) <> ")"
|
||||
end
|
||||
|
||||
defp call_args([]), do: ""
|
||||
|
||||
defp call_args(args) do
|
||||
"(" <> Enum.map_join(args, ", ", fn %{"name" => n} -> "#{n}: $#{n}" end) <> ")"
|
||||
end
|
||||
|
||||
defp selection_block(""), do: ""
|
||||
defp selection_block(selection), do: " { " <> selection <> " }"
|
||||
end
|
||||
79
vendor/gaiia/lib/gaiia/pagination.ex
vendored
Normal file
79
vendor/gaiia/lib/gaiia/pagination.ex
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
defmodule Gaiia.Pagination do
|
||||
@moduledoc """
|
||||
Helpers for traversing Relay-style cursor pagination over the Gaiia API.
|
||||
|
||||
The GraphQL schema exposes paginated lists with a `nodes` field and a
|
||||
`pageInfo { hasNextPage endCursor }` envelope. `stream/4` follows
|
||||
these cursors transparently and yields individual node maps.
|
||||
|
||||
## Example
|
||||
|
||||
query = ~S\"\"\"
|
||||
query Accounts($first: Int, $after: String) {
|
||||
accounts(first: $first, after: $after) {
|
||||
nodes { id name }
|
||||
pageInfo { hasNextPage endCursor }
|
||||
}
|
||||
}
|
||||
\"\"\"
|
||||
|
||||
client
|
||||
|> Gaiia.Pagination.stream(query, %{"first" => 50}, path: ["accounts"])
|
||||
|> Stream.take(200)
|
||||
|> Enum.to_list()
|
||||
"""
|
||||
|
||||
alias Gaiia.Client
|
||||
|
||||
@type opts :: [
|
||||
path: [String.t()],
|
||||
cursor_variable: String.t()
|
||||
]
|
||||
|
||||
@doc """
|
||||
Stream all nodes from a paginated GraphQL field.
|
||||
|
||||
## Options
|
||||
|
||||
* `:path` — A list of keys describing where the connection lives inside
|
||||
the `data` payload, for example `["accounts"]`. Required.
|
||||
* `:cursor_variable` — Variable name for the after-cursor (default `"after"`).
|
||||
"""
|
||||
@spec stream(Client.t(), String.t(), map(), opts()) :: Enumerable.t()
|
||||
def stream(%Client{} = client, query, variables, opts) do
|
||||
path = Keyword.fetch!(opts, :path)
|
||||
cursor_var = Keyword.get(opts, :cursor_variable, "after")
|
||||
|
||||
Stream.resource(
|
||||
fn -> {:cont, nil} end,
|
||||
&next_page(&1, client, query, variables, path, cursor_var),
|
||||
fn _ -> :ok end
|
||||
)
|
||||
end
|
||||
|
||||
defp next_page(:halt, _client, _query, _variables, _path, _cursor_var), do: {:halt, :halt}
|
||||
|
||||
defp next_page({:cont, cursor}, client, query, variables, path, cursor_var) do
|
||||
vars = put_cursor(variables, cursor_var, cursor)
|
||||
|
||||
client
|
||||
|> Client.query(query, vars)
|
||||
|> handle_page(path)
|
||||
end
|
||||
|
||||
defp put_cursor(variables, _cursor_var, nil), do: variables
|
||||
defp put_cursor(variables, cursor_var, cursor), do: Map.put(variables, cursor_var, cursor)
|
||||
|
||||
defp handle_page({:ok, data}, path) do
|
||||
connection = get_in(data, path) || %{}
|
||||
nodes = Map.get(connection, "nodes", [])
|
||||
page_info = Map.get(connection, "pageInfo", %{})
|
||||
{nodes, advance_from(page_info)}
|
||||
end
|
||||
|
||||
defp handle_page({:error, error}, _path), do: raise(error)
|
||||
|
||||
defp advance_from(%{"hasNextPage" => true, "endCursor" => cursor}) when is_binary(cursor), do: {:cont, cursor}
|
||||
|
||||
defp advance_from(_page_info), do: :halt
|
||||
end
|
||||
50
vendor/gaiia/lib/gaiia/queries.ex
vendored
Normal file
50
vendor/gaiia/lib/gaiia/queries.ex
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
defmodule Gaiia.Queries do
|
||||
@moduledoc """
|
||||
Auto-generated functions for every query operation in the Gaiia
|
||||
GraphQL API.
|
||||
|
||||
Each function has the signature:
|
||||
|
||||
<name>(client, variables \\\\ %{}, selection \\\\ "", opts \\\\ [])
|
||||
:: {:ok, map() | term()} | {:error, Gaiia.Error.t()}
|
||||
|
||||
Arguments:
|
||||
|
||||
* `client` — a `Gaiia.Client` struct (build with `Gaiia.Client.new/1`)
|
||||
* `variables` — map of variables matching the GraphQL argument names
|
||||
(camelCase, string keys). Only keys present in this map are sent.
|
||||
* `selection` — selection-set body (no surrounding braces). Required
|
||||
for object return types; pass `""` for scalar return types.
|
||||
* `opts` — forwarded to `Gaiia.Client.query/4` (e.g. `:operation_name`)
|
||||
|
||||
Return value is the contents of the named field, unwrapped from the
|
||||
`data` envelope — i.e. `Gaiia.Queries.account(client, %{"id" => id}, "id")`
|
||||
returns `{:ok, %{"id" => "..."}}`, not `{:ok, %{"account" => %{"id" => "..."}}}`.
|
||||
"""
|
||||
|
||||
alias Gaiia.Client
|
||||
alias Gaiia.Operation
|
||||
alias Gaiia.Schema
|
||||
|
||||
for op <- Schema.queries() do
|
||||
@name op["name"]
|
||||
@fun_name Schema.function_name(@name)
|
||||
@args op["args"]
|
||||
@description op["description"] || "GraphQL query `#{@name}`."
|
||||
|
||||
@doc """
|
||||
#{@description}
|
||||
|
||||
GraphQL: `query { #{@name} }`
|
||||
"""
|
||||
@spec unquote(@fun_name)(Client.t(), map(), String.t(), keyword()) ::
|
||||
{:ok, term()} | {:error, Gaiia.Error.t()}
|
||||
def unquote(@fun_name)(client, variables \\ %{}, selection \\ "", opts \\ []) do
|
||||
operation = Operation.build(:query, unquote(@name), unquote(Macro.escape(@args)), variables, selection)
|
||||
|
||||
with {:ok, data} <- Client.query(client, operation, variables, opts) do
|
||||
{:ok, Map.get(data, unquote(@name))}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
vendor/gaiia/lib/gaiia/schema.ex
vendored
Normal file
33
vendor/gaiia/lib/gaiia/schema.ex
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
defmodule Gaiia.Schema do
|
||||
@moduledoc """
|
||||
Compile-time access to the Gaiia GraphQL schema.
|
||||
|
||||
At compile time this module loads the operation specs from
|
||||
`priv/queries.json` and `priv/mutations.json` (extracted from the
|
||||
full introspection schema) so generator modules can iterate over
|
||||
every operation.
|
||||
"""
|
||||
|
||||
@queries_path :gaiia |> :code.priv_dir() |> to_string() |> Path.join("queries.json")
|
||||
@mutations_path :gaiia |> :code.priv_dir() |> to_string() |> Path.join("mutations.json")
|
||||
|
||||
@external_resource @queries_path
|
||||
@external_resource @mutations_path
|
||||
|
||||
@queries @queries_path |> File.read!() |> Jason.decode!()
|
||||
@mutations @mutations_path |> File.read!() |> Jason.decode!()
|
||||
|
||||
# The compile-time-loaded literals would force dialyzer to infer the
|
||||
# full nested map shape; we keep these specless so callers can treat
|
||||
# them as the opaque "introspection envelope" they are.
|
||||
|
||||
@doc "Return the list of all query operation specs."
|
||||
def queries, do: @queries
|
||||
|
||||
@doc "Return the list of all mutation operation specs."
|
||||
def mutations, do: @mutations
|
||||
|
||||
@doc "Convert a camelCase GraphQL name to an Elixir-friendly snake_case atom."
|
||||
@spec function_name(String.t()) :: atom()
|
||||
def function_name(camel_name), do: camel_name |> Macro.underscore() |> String.to_atom()
|
||||
end
|
||||
26
vendor/gaiia/lib/gaiia/type_ref.ex
vendored
Normal file
26
vendor/gaiia/lib/gaiia/type_ref.ex
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
defmodule Gaiia.TypeRef do
|
||||
@moduledoc """
|
||||
Helpers for working with GraphQL type-reference maps as returned by
|
||||
introspection (`__Type`).
|
||||
|
||||
A type reference is a recursive map of the shape:
|
||||
|
||||
%{"kind" => "NON_NULL" | "LIST" | "SCALAR" | ..., "name" => name, "ofType" => inner_or_nil}
|
||||
|
||||
This module renders such structures to GraphQL syntax (`Int!`,
|
||||
`[String!]!`, etc.) and unwraps modifiers to find the underlying named type.
|
||||
"""
|
||||
|
||||
@type t :: %{required(String.t()) => term()}
|
||||
|
||||
@doc "Render a type-reference map as a GraphQL type string."
|
||||
@spec render(t()) :: String.t()
|
||||
def render(%{"kind" => "NON_NULL", "ofType" => inner}), do: render(inner) <> "!"
|
||||
def render(%{"kind" => "LIST", "ofType" => inner}), do: "[" <> render(inner) <> "]"
|
||||
def render(%{"name" => name}) when is_binary(name), do: name
|
||||
|
||||
@doc "Unwrap NON_NULL/LIST modifiers to return the innermost named type ref."
|
||||
@spec named_type(t()) :: t()
|
||||
def named_type(%{"kind" => kind, "ofType" => inner}) when kind in ["NON_NULL", "LIST"], do: named_type(inner)
|
||||
def named_type(%{"name" => name} = type) when is_binary(name), do: type
|
||||
end
|
||||
35
vendor/gaiia/mix.exs
vendored
Normal file
35
vendor/gaiia/mix.exs
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
defmodule Gaiia.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :gaiia,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.19",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
deps: deps(),
|
||||
dialyzer: [
|
||||
plt_add_apps: [:ex_unit],
|
||||
flags: [:error_handling, :underspecs, :unmatched_returns]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
[extra_applications: [:logger]]
|
||||
end
|
||||
|
||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||
defp elixirc_paths(_), do: ["lib"]
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:req, "~> 0.5"},
|
||||
{:jason, "~> 1.4"},
|
||||
{:styler, "~> 1.5", only: [:dev, :test], runtime: false},
|
||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}
|
||||
]
|
||||
end
|
||||
end
|
||||
17
vendor/gaiia/mix.lock
vendored
Normal file
17
vendor/gaiia/mix.lock
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
%{
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [: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", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
|
||||
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
|
||||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
"jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
|
||||
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
||||
"mint": {:hex, :mint, "1.8.0", "b964eaf4416f2dee2ba88968d52239fca5621b0402b9c95f55a08eb9d74803e9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "f3c572c11355eccf00f22275e9b42463bc17bd28db13be1e28f8e0bb4adbc849"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
|
||||
"styler": {:hex, :styler, "1.11.0", "35010d970689a23c2bcc8e97bd8bf7d20e3561d60c49be84654df5c37d051a9c", [:mix], [], "hexpm", "70f36165d0cf238a32b7a456fdef6a9c72e77e657d7ac4a0ace33aeba3f2b8c0"},
|
||||
"telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},
|
||||
}
|
||||
4574
vendor/gaiia/priv/mutations.json
vendored
Normal file
4574
vendor/gaiia/priv/mutations.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
6806
vendor/gaiia/priv/queries.json
vendored
Normal file
6806
vendor/gaiia/priv/queries.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue