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:
Graham McIntire 2026-05-11 14:46:27 -05:00
parent 485748d253
commit 1f9fe2ee29
23 changed files with 12130 additions and 438 deletions

View file

@ -25,13 +25,6 @@ config :esbuild,
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} 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 # Configure Elixir's Logger
config :logger, :default_formatter, config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n", format: "$time $metadata[$level] $message\n",

View file

@ -1,12 +1,5 @@
import Config 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 # Console backend configuration
config :logger, :console, config :logger, :console,
format: "[$level] $message\n", format: "[$level] $message\n",

View file

@ -3,8 +3,7 @@ defmodule Towerops.ErrorTrackerIgnorer do
Drops benign errors from ErrorTracker that occur during K8s pod shutdown Drops benign errors from ErrorTracker that occur during K8s pod shutdown
or transient DB connection cycling. or transient DB connection cycling.
Matches the same patterns as `Towerops.HoneybadgerFilter` and Matches the same patterns as `Towerops.LoggerFilters.drop_shutdown_errors/2`.
`Towerops.LoggerFilters.drop_shutdown_errors/2`.
""" """
@behaviour ErrorTracker.Ignorer @behaviour ErrorTracker.Ignorer

View file

@ -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

View file

@ -49,10 +49,6 @@ defmodule ToweropsWeb.Endpoint do
from: Application.compile_env(:towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}), from: Application.compile_env(:towerops, :coverage_storage_dir, {:towerops, "priv/static/coverage"}),
gzip: false gzip: false
if Mix.env() == :dev do
plug Tidewave
end
# Code reloading can be explicitly enabled under the # Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint. # :code_reloader configuration of your endpoint.
if code_reloading? do if code_reloading? do

View file

@ -2,7 +2,6 @@ defmodule ToweropsWeb.Router do
@moduledoc false @moduledoc false
use ToweropsWeb, :router use ToweropsWeb, :router
use Honeybadger.Plug
use ErrorTracker.Web, :router use ErrorTracker.Web, :router
import Oban.Web.Router import Oban.Web.Router

View file

@ -96,8 +96,8 @@ defmodule Towerops.MixProject do
github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", app: false, compile: false, depth: 1}, github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", app: false, compile: false, depth: 1},
{:swoosh, "~> 1.24"}, {:swoosh, "~> 1.24"},
{:gen_smtp, "~> 1.0"}, {:gen_smtp, "~> 1.0"},
{:cbor, "~> 1.0"},
{:req, "~> 0.5"}, {:req, "~> 0.5"},
{:gaiia, path: "vendor/gaiia"},
{:castore, "~> 1.0"}, {:castore, "~> 1.0"},
{:sweet_xml, "~> 0.7"}, {:sweet_xml, "~> 0.7"},
{:yaml_elixir, "~> 2.9"}, {:yaml_elixir, "~> 2.9"},
@ -120,7 +120,6 @@ defmodule Towerops.MixProject do
{:absinthe, "~> 1.7"}, {:absinthe, "~> 1.7"},
{:absinthe_plug, "~> 1.5"}, {:absinthe_plug, "~> 1.5"},
{:absinthe_phoenix, "~> 2.0"}, {:absinthe_phoenix, "~> 2.0"},
{:honeybadger, "~> 0.24"},
{:error_tracker, "~> 0.7"}, {:error_tracker, "~> 0.7"},
{:error_tracker_notifier, "~> 0.2"}, {:error_tracker_notifier, "~> 0.2"},
{:stream_data, "~> 1.1", only: :test}, {:stream_data, "~> 1.1", only: :test},
@ -134,8 +133,7 @@ defmodule Towerops.MixProject do
{:cloak_ecto, "~> 1.3"}, {:cloak_ecto, "~> 1.3"},
{:geo_postgis, "~> 3.7"}, {:geo_postgis, "~> 3.7"},
{:logger_file_backend, "~> 0.0.13", only: :dev}, {:logger_file_backend, "~> 0.0.13", only: :dev},
{:logger_backends, "~> 1.0", only: :dev}, {:logger_backends, "~> 1.0", only: :dev}
{:tidewave, "~> 0.5", only: :dev}
] ]
end end

View file

@ -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"}, "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"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.18", "5e43ef0ec7d31195dfa5a65a86e6131db999d074179d2ba5a8de11fe14570f55", [:mix], [], "hexpm", "f393e4fe6317829b158fb74d86eb681f737d2fe326aa61ccf6293c4104957e34"}, "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"}, "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"}, "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": {: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"}, "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"}, "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"}, "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"}, "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]}, "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"}, "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"}, "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"}, "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"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "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"}, "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"}, "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"}, "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"}, "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_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"}, "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"}, "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"}, "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"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

6806
vendor/gaiia/priv/queries.json vendored Normal file

File diff suppressed because it is too large Load diff