defmodule Plausible.Release do use Plausible use Plausible.Repo require Logger @app :plausible @start_apps [ :ssl, :postgrex, :ch, :ecto ] def should_be_first_launch? do on_full_build do false else not (_has_users? = Repo.exists?(Plausible.Auth.User)) end end def migrate do prepare() Enum.each(repos(), &run_migrations_for/1) IO.puts("Migrations successful!") end def pending_migrations do prepare() IO.puts("Pending migrations") IO.puts("") Enum.each(repos(), &list_pending_migrations_for/1) end def seed do prepare() # Run seed script Enum.each(repos(), &run_seeds_for/1) # Signal shutdown IO.puts("Success!") end def createdb do prepare() for repo <- repos() do :ok = ensure_repo_created(repo) end IO.puts("Creation of Db successful!") end def rollback do prepare() get_step = IO.gets("Enter the number of steps: ") |> String.trim() |> Integer.parse() case get_step do {int, _trailing} -> Enum.each(repos(), fn repo -> run_rollbacks_for(repo, int) end) IO.puts("Rollback successful!") :error -> IO.puts("Invalid integer") end end def configure_ref_inspector() do priv_dir = Application.app_dir(:plausible, "priv/ref_inspector") Application.put_env(:ref_inspector, :database_path, priv_dir) end def configure_ua_inspector() do priv_dir = Application.app_dir(:plausible, "priv/ua_inspector") Application.put_env(:ua_inspector, :database_path, priv_dir) end def dump_plans() do prepare() Repo.delete_all("plans") plans = Plausible.Billing.Plans.all() |> Plausible.Billing.Plans.with_prices() |> Enum.map(fn plan -> plan = Map.from_struct(plan) monthly_cost = plan.monthly_cost && Money.to_decimal(plan.monthly_cost) yearly_cost = plan.yearly_cost && Money.to_decimal(plan.yearly_cost) {:ok, features} = Plausible.Billing.Ecto.FeatureList.dump(plan.features) {:ok, team_member_limit} = Plausible.Billing.Ecto.Limit.dump(plan.team_member_limit) plan |> Map.drop([:id]) |> Map.put(:kind, Atom.to_string(plan.kind)) |> Map.put(:monthly_cost, monthly_cost) |> Map.put(:yearly_cost, yearly_cost) |> Map.put(:features, features) |> Map.put(:team_member_limit, team_member_limit) end) {count, _} = Repo.insert_all("plans", plans) IO.puts("Inserted #{count} plans") end ############################## defp repos do Application.fetch_env!(@app, :ecto_repos) end defp run_seeds_for(repo) do # Run the seed script if it exists seed_script = seeds_path(repo) if File.exists?(seed_script) do IO.puts("Running seed script..") Code.eval_file(seed_script) end end defp run_migrations_for(repo) do IO.puts("Running migrations for #{repo}") {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) end defp list_pending_migrations_for(repo) do IO.puts("Listing pending migrations for #{repo}") IO.puts("") migration_directory = Ecto.Migrator.migrations_path(repo) pending = repo |> Ecto.Migrator.migrations([migration_directory]) |> Enum.filter(fn {status, _version, _migration} -> status == :down end) if pending == [] do IO.puts("No pending migrations") else Enum.each(pending, fn {_, version, migration} -> IO.puts("* #{version}_#{migration}") end) end IO.puts("") end defp ensure_repo_created(repo) do IO.puts("create #{inspect(repo)} database if it doesn't exist") case repo.__adapter__.storage_up(repo.config) do :ok -> :ok {:error, :already_up} -> :ok {:error, term} -> {:error, term} end end defp run_rollbacks_for(repo, step) do app = Keyword.get(repo.config, :otp_app) IO.puts("Running rollbacks for #{app} (STEP=#{step})") {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, all: false, step: step)) end defp prepare do IO.puts("Loading #{@app}..") # Load the code for myapp, but don't start it :ok = Application.ensure_loaded(@app) IO.puts("Starting dependencies..") # Start apps necessary for executing migrations Enum.each(@start_apps, &Application.ensure_all_started/1) # Start the Repo(s) for myapp IO.puts("Starting repos..") Enum.each(repos(), & &1.start_link(pool_size: 2)) end defp seeds_path(repo), do: priv_path_for(repo, "seeds.exs") defp priv_path_for(repo, filename) do app = Keyword.get(repo.config, :otp_app) IO.puts("App: #{app}") repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore() Path.join([priv_dir(app), repo_underscore, filename]) end defp priv_dir(app), do: "#{:code.priv_dir(app)}" end