From bfc97ab60857ab34a51a56112a4bd14fa018c808 Mon Sep 17 00:00:00 2001 From: Grejdi Gjura Date: Tue, 20 Feb 2024 22:53:48 -0500 Subject: [PATCH] feat: add admin app --- admin/.credo.exs | 211 ++++++++++++++++++ admin/.formatter.exs | 4 + admin/.gitignore | 28 +++ admin/README.md | 23 ++ admin/config/config.exs | 13 ++ admin/config/dev.exs | 10 + admin/config/prod.exs | 13 ++ admin/config/runtime.exs | 16 ++ admin/config/test.exs | 6 + admin/lib/admin/application.ex | 28 +++ admin/lib/admin/repo.ex | 42 ++++ admin/lib/admin/router.ex | 71 ++++++ .../admin/schemas/cubic_ods_table_snapshot.ex | 42 ++++ admin/lib/admin/schemas/cubic_table.ex | 41 ++++ admin/lib/admin/templates/index.html | 14 ++ admin/mix.exs | 41 ++++ admin/mix.lock | 27 +++ admin/test/test_helper.exs | 1 + 18 files changed, 631 insertions(+) create mode 100644 admin/.credo.exs create mode 100644 admin/.formatter.exs create mode 100644 admin/.gitignore create mode 100644 admin/README.md create mode 100644 admin/config/config.exs create mode 100644 admin/config/dev.exs create mode 100644 admin/config/prod.exs create mode 100644 admin/config/runtime.exs create mode 100644 admin/config/test.exs create mode 100644 admin/lib/admin/application.ex create mode 100644 admin/lib/admin/repo.ex create mode 100644 admin/lib/admin/router.ex create mode 100644 admin/lib/admin/schemas/cubic_ods_table_snapshot.ex create mode 100644 admin/lib/admin/schemas/cubic_table.ex create mode 100644 admin/lib/admin/templates/index.html create mode 100644 admin/mix.exs create mode 100644 admin/mix.lock create mode 100644 admin/test/test_helper.exs diff --git a/admin/.credo.exs b/admin/.credo.exs new file mode 100644 index 00000000..bc93942a --- /dev/null +++ b/admin/.credo.exs @@ -0,0 +1,211 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "config/", + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + # matches 'py_cubic_ingestion/pyprojest.toml' + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, false}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, [max_deps: 15]}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Warning.LazyLogging, []} + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/admin/.formatter.exs b/admin/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/admin/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 00000000..fc2f44b9 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,28 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +admin-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +priv/cert/* diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 00000000..d87cbdca --- /dev/null +++ b/admin/README.md @@ -0,0 +1,23 @@ +# Admin + +An Elixir app to get visibility into the Data Platform pipelines and take action on them. + +## Installation + +1. Initialize app. +```sh +mix deps.get +``` + +2. Generate SSL certificates. + +```sh +mix x509.gen.selfsigned +``` + +3. Run the app. +```sh +iex -S mix +``` + +4. Go to [https://localhost:4000/](https://localhost:4000/) diff --git a/admin/config/config.exs b/admin/config/config.exs new file mode 100644 index 00000000..bb31b02b --- /dev/null +++ b/admin/config/config.exs @@ -0,0 +1,13 @@ +import Config + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +config :admin, + ecto_repos: [Admin.Repo] + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{Mix.env()}.exs" diff --git a/admin/config/dev.exs b/admin/config/dev.exs new file mode 100644 index 00000000..4f04fad0 --- /dev/null +++ b/admin/config/dev.exs @@ -0,0 +1,10 @@ +import Config + +config :ex_aws, + # note: 'region' doesn't work. it's inherited from the profile + # region: [{:system, "AWS_REGION"}, {:awscli, "default", 30}, :instance_role], + access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, {:awscli, "default", 30}], + secret_access_key: [ + {:system, "AWS_SECRET_ACCESS_KEY"}, + {:awscli, "default", 30} + ] diff --git a/admin/config/prod.exs b/admin/config/prod.exs new file mode 100644 index 00000000..ad429764 --- /dev/null +++ b/admin/config/prod.exs @@ -0,0 +1,13 @@ +import Config + +config :admin, Admin.Repo, + show_sensitive_data_on_connection_error: false, + ssl: true, + use_iam_token: true + +config :ex_aws, + # overwrite defaults here, so as to only look at instance role + access_key_id: :instance_role, + secret_access_key: :instance_role + +config :ehmon, :report_mf, {:ehmon, :info_report} diff --git a/admin/config/runtime.exs b/admin/config/runtime.exs new file mode 100644 index 00000000..0ac8054b --- /dev/null +++ b/admin/config/runtime.exs @@ -0,0 +1,16 @@ +import Config + +config :admin, Admin.Repo, + username: System.get_env("DB_USER"), + database: System.get_env("DB_NAME"), + hostname: System.get_env("DB_HOST"), + password: System.get_env("DB_PASSWORD"), + port: "DB_PORT" |> System.get_env("5432") |> String.to_integer(), + configure: {Admin.Repo, :before_connect, []}, + # default to 10 + pool_size: "DB_POOL_SIZE" |> System.get_env("10") |> String.to_integer() + +# for testing, we'd like to change the database +if config_env() == :test do + config :admin, Admin.Repo, database: "#{System.get_env("DB_NAME")}_test" +end diff --git a/admin/config/test.exs b/admin/config/test.exs new file mode 100644 index 00000000..6734fa19 --- /dev/null +++ b/admin/config/test.exs @@ -0,0 +1,6 @@ +import Config + +# only log warnings+ in test +config :logger, level: :info + +config :admin, Admin.Repo, pool: Ecto.Adapters.SQL.Sandbox diff --git a/admin/lib/admin/application.ex b/admin/lib/admin/application.ex new file mode 100644 index 00000000..aded9296 --- /dev/null +++ b/admin/lib/admin/application.ex @@ -0,0 +1,28 @@ +defmodule Admin.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + {Admin.Repo, []}, + {Plug.Cowboy, + scheme: :https, + options: [ + certfile: "priv/cert/selfsigned.pem", + keyfile: "priv/cert/selfsigned_key.pem", + otp_app: :admin, + port: 4000 + ], + plug: Admin.Router} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Admin.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/admin/lib/admin/repo.ex b/admin/lib/admin/repo.ex new file mode 100644 index 00000000..26b97c4f --- /dev/null +++ b/admin/lib/admin/repo.ex @@ -0,0 +1,42 @@ +defmodule Admin.Repo do + use Ecto.Repo, + otp_app: :admin, + adapter: Ecto.Adapters.Postgres + + require Logger + require ExAws.RDS + + @doc """ + Set via the `:configure` option in the Admin.Repo configuration, a function + invoked prior to each DB connection. `config` is the configured connection values + and it returns a new set of config values to be used when connecting. + """ + @spec before_connect(map()) :: map() + def before_connect(config) do + repo_config = Application.get_env(:admin, Admin.Repo, []) + + # generate a token as a password for RDS database if indicated to + if Keyword.get(repo_config, :use_iam_token, false) do + aws_rds = Keyword.get(repo_config, :lib_ex_aws_rds, ExAws.RDS) + + username = Keyword.fetch!(config, :username) + hostname = Keyword.fetch!(config, :hostname) + port = Keyword.fetch!(config, :port) + token = aws_rds.generate_db_auth_token(hostname, username, port, %{}) + + # update password with token and update ssl options (if set) to ref rds cert + Keyword.merge(config, + password: token, + ssl_opts: [ + cacertfile: Application.app_dir(:admin, ["priv", "aws-cert-bundle.pem"]), + verify: :verify_peer, + server_name_indication: String.to_charlist(hostname), + verify_fun: + {&:ssl_verify_hostname.verify_fun/3, [check_hostname: String.to_charlist(hostname)]} + ] + ) + else + config + end + end +end diff --git a/admin/lib/admin/router.ex b/admin/lib/admin/router.ex new file mode 100644 index 00000000..0dd82355 --- /dev/null +++ b/admin/lib/admin/router.ex @@ -0,0 +1,71 @@ +defmodule Admin.Router do + use Plug.Router + + import Ecto.Query + + require Logger + + alias Admin.Repo + alias Admin.Schema.CubicTable + + @template_dir "lib/admin/templates" + + plug(Plug.SSL, rewrite_on: [:x_forwarded_proto, :x_forwarded_host, :x_forwarded_port]) + + plug(Plug.Parsers, + pass: ["text/*"], + parsers: [:multipart, :json], + json_decoder: Jason + ) + + plug(:match) + plug(:dispatch) + + get "/" do + render(conn, "index.html") + end + + get "/_json" do + render_json(conn, %{}) + end + + get "/_health" do + send_resp(conn, 200, "Healthy!") + end + + get "/test" do + tables = Repo.all(from(table in CubicTable, where: is_nil(table.deleted_at))) + + Logger.info("[admin] #{length(tables)}") + + render(conn, "index.html") + end + + get "/admin" do + Logger.info("[admin] Protected") + + render(conn, "index.html") + end + + match _ do + send_resp(conn, 404, "Not Found") + end + + # private + defp render(%{status: status} = conn, template, assigns \\ []) do + body = + @template_dir + |> Path.join(template) + |> EEx.eval_file(assigns) + + send_resp(conn, status || 200, body) + end + + defp render_json(%{status: status} = conn, data) do + body = Jason.encode!(data) + + conn + |> put_resp_content_type("application/json") + |> send_resp(status || 200, body) + end +end diff --git a/admin/lib/admin/schemas/cubic_ods_table_snapshot.ex b/admin/lib/admin/schemas/cubic_ods_table_snapshot.ex new file mode 100644 index 00000000..a07c47cb --- /dev/null +++ b/admin/lib/admin/schemas/cubic_ods_table_snapshot.ex @@ -0,0 +1,42 @@ +defmodule Admin.Schema.CubicOdsTableSnapshot do + @moduledoc """ + ODS tables need to keep track of additional infomation. This table serves for storing + the snapshot when one is created and uploaded to the 'incoming' S3 bucket. + The snapshot_s3_key is how we know which object indicates that a new snapshot has been + created. If this object is passing through, then we need to update our snapshot value. + """ + use Ecto.Schema + + import Ecto.Query + + @derive {Jason.Encoder, + only: [ + :id, + :table_id, + :snapshot, + :snapshot_s3_key, + :deleted_at, + :inserted_at, + :updated_at + ]} + + @type t :: %__MODULE__{ + id: integer() | nil, + table_id: integer() | nil, + snapshot: DateTime.t() | nil, + snapshot_s3_key: String.t() | nil, + deleted_at: DateTime.t() | nil, + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil + } + + schema "cubic_ods_table_snapshots" do + field(:table_id, :integer) + field(:snapshot, :utc_datetime) + field(:snapshot_s3_key, :string) + + field(:deleted_at, :utc_datetime) + + timestamps(type: :utc_datetime) + end +end diff --git a/admin/lib/admin/schemas/cubic_table.ex b/admin/lib/admin/schemas/cubic_table.ex new file mode 100644 index 00000000..e72e2964 --- /dev/null +++ b/admin/lib/admin/schemas/cubic_table.ex @@ -0,0 +1,41 @@ +defmodule Admin.Schema.CubicTable do + @moduledoc """ + Contains a list of prefixes that are allowed to be processed through the 'incoming' S3 bucket. + The name also identifies the table in the Glue Data Catalog databases. + """ + use Ecto.Schema + + @derive {Jason.Encoder, + only: [ + :id, + :name, + :s3_prefix, + :is_raw, + :is_active, + :deleted_at, + :inserted_at, + :updated_at + ]} + + @type t :: %__MODULE__{ + id: integer() | nil, + name: String.t() | nil, + s3_prefix: String.t() | nil, + is_raw: boolean() | nil, + is_active: boolean() | nil, + deleted_at: DateTime.t() | nil, + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil + } + + schema "cubic_tables" do + field(:name, :string) + field(:s3_prefix, :string) + field(:is_raw, :boolean) + field(:is_active, :boolean) + + field(:deleted_at, :utc_datetime) + + timestamps(type: :utc_datetime) + end +end diff --git a/admin/lib/admin/templates/index.html b/admin/lib/admin/templates/index.html new file mode 100644 index 00000000..e1484693 --- /dev/null +++ b/admin/lib/admin/templates/index.html @@ -0,0 +1,14 @@ + + + + + + Admin + + + + +OK + + + diff --git a/admin/mix.exs b/admin/mix.exs new file mode 100644 index 00000000..d0ddf27b --- /dev/null +++ b/admin/mix.exs @@ -0,0 +1,41 @@ +defmodule Admin.MixProject do + use Mix.Project + + def project do + [ + app: :admin, + version: "0.1.0", + elixir: "~> 1.13", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {Admin.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.4", only: :dev, runtime: false}, + {:ecto_sql, "~> 3.11"}, + {:ex_aws, "~> 2.5"}, + {:ex_aws_rds, "~> 2.0"}, + {:jason, "~> 1.4"}, + {:lcov_ex, "~> 0.3.3", only: [:dev, :test], runtime: false}, + {:plug, "~> 1.15"}, + {:plug_cowboy, "~> 2.6"}, + {:postgrex, "~> 0.17.4"}, + {:ssl_verify_fun, "~> 1.1"}, + {:x509, "~> 0.8.8", only: :dev, runtime: false} + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/admin/mix.lock b/admin/mix.lock new file mode 100644 index 00000000..be133cd6 --- /dev/null +++ b/admin/mix.lock @@ -0,0 +1,27 @@ +%{ + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, + "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [: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", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_aws": {:hex, :ex_aws, "2.5.1", "7418917974ea42e9e84b25e88b9f3d21a861d5f953ad453e212f48e593d8d39f", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1b95431f70c446fa1871f0eb9b183043c5a625f75f9948a42d25f43ae2eff12b"}, + "ex_aws_rds": {:hex, :ex_aws_rds, "2.0.2", "38dd8e83d57cf4b7286c4f6f5c978f700c40c207ffcdd6ca5d738e5eba933f9a", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}], "hexpm", "9e5b5cc168077874cbd0d29ba65d01caf1877e705fb5cecacf0667dd19bfa75c"}, + "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "lcov_ex": {:hex, :lcov_ex, "0.3.3", "1745a88e46606c4f86408299f54878b7d0cd22ea3e9c54b0018b6ed631a9ce87", [:mix], [], "hexpm", "ea373ec4d2df213357c5a464be16ab08d1e58e61ea2de784a483780c22a1e74a"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.0", "3ae9369c60641084363b08fe90267cbdd316df57e3557ea522114b30b63256ea", [:mix], [{:cowboy, "~> 2.7.0 or ~> 2.8.0 or ~> 2.9.0 or ~> 2.10.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d85444fb8aa1f2fc62eabe83bbe387d81510d773886774ebdcb429b3da3c1a4a"}, + "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.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", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "x509": {:hex, :x509, "0.8.8", "aaf5e58b19a36a8e2c5c5cff0ad30f64eef5d9225f0fd98fb07912ee23f7aba3", [:mix], [], "hexpm", "ccc3bff61406e5bb6a63f06d549f3dba3a1bbb456d84517efaaa210d8a33750f"}, +} diff --git a/admin/test/test_helper.exs b/admin/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/admin/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()