defmodule Mix.Tasks.Keksdose.Install do
@moduledoc """
Generates the migration for the `consent_records` table.
mix keksdose.install
mix keksdose.install --adapter mysql
mix keksdose.install --adapter sqlite
Detects the configured repo's adapter and emits the right column type for
`categories` (jsonb / json / text). Override with `--adapter`.
"""
use Mix.Task
@shortdoc "Generates the keksdose migration"
@template_path Path.expand("../../../priv/templates/add_consent_records_table.exs.eex", __DIR__)
@adapter_columns %{
postgres: ":jsonb",
mysql: ":json",
sqlite: ":text",
other: ":text"
}
@adapter_module_map %{
Ecto.Adapters.Postgres => :postgres,
Ecto.Adapters.MyXQL => :mysql,
Ecto.Adapters.SQLite3 => :sqlite,
Ecto.Adapters.Tds => :other
}
@impl Mix.Task
def run(args) do
opts = opts(args)
template_path = Keyword.get(opts, :template_path, @template_path)
migrations_dir = Keyword.get(opts, :migrations_dir, Path.join(["priv", "repo", "migrations"]))
module_prefix = Keyword.get(opts, :module_prefix, infer_module_prefix())
adapter = resolve_adapter(opts)
categories_column = Map.fetch!(@adapter_columns, adapter)
File.mkdir_p!(migrations_dir)
filename = "#{timestamp()}_add_consent_records_table.exs"
target = Path.join(migrations_dir, filename)
body =
EEx.eval_file(template_path,
module_prefix: module_prefix,
categories_column: categories_column
)
File.write!(target, body)
Mix.shell().info("""
Created #{target}
adapter: #{adapter}
categories column type: #{categories_column}
Next steps:
1. Add to config/config.exs:
config :keksdose, repo: #{module_prefix}.Repo
2. Run the migration:
mix ecto.migrate
3. Mount the plug in your router (pick any path):
forward "/api/cookie-consents", Keksdose.PlugHandler
4. Inject the endpoint URL into your layout:
<%= raw Keksdose.FrontendConfig.inject_script("/api/cookie-consents") %>
""")
target
end
defp opts(args) do
{opts, _, _} =
OptionParser.parse(args,
strict: [
template_path: :string,
migrations_dir: :string,
module_prefix: :string,
adapter: :string
]
)
opts
end
defp resolve_adapter(opts) do
case Keyword.get(opts, :adapter) do
flag when is_binary(flag) -> parse_adapter!(flag)
nil -> adapter_from_configured_repo()
end
end
defp adapter_from_configured_repo do
with repo when is_atom(repo) and not is_nil(repo) <-
Application.get_env(:keksdose, :repo),
mod when not is_nil(mod) <- safe_repo_adapter(repo) do
Map.get(@adapter_module_map, mod, :other)
else
_ -> :postgres
end
end
defp safe_repo_adapter(repo) do
repo.__adapter__()
rescue
_ -> nil
end
defp parse_adapter!(flag) do
case String.downcase(flag) do
"postgres" -> :postgres
"postgresql" -> :postgres
"mysql" -> :mysql
"myxql" -> :mysql
"sqlite" -> :sqlite
"sqlite3" -> :sqlite
"other" -> :other
_ -> Mix.raise("Unknown --adapter: #{flag}. Expected postgres | mysql | sqlite.")
end
end
defp infer_module_prefix do
app = Mix.Project.config()[:app] || :my_app
app |> Atom.to_string() |> Macro.camelize()
end
defp timestamp do
Calendar.strftime(DateTime.utc_now(), "%Y%m%d%H%M%S")
end
end