analytics/lib/plausible_release.ex
Adrian Gruntkowski 1c5c4a25aa
HelpScout integration (#4327)
* Implement basic HelpScout integration

* Set 127.0.0.1 as a default customer IP in `Plans.with_prices/2`

* Use `is_nil/1` instead of `... == nil` (h/t @aerosol)

* Use `Path.join/1,2` to build API URLs a bit more safely (h/t @aerosol)

* Check for locked sites entirely within query logic

* Move conditional start of HelpScout vault to compile-time

* Include customer_id in error sent to Sentry

* Use `Plug.Crypto.secure_compare/2` for constant-time signature comparison

* Refactor token request function

* Use `Path.join/1` in one more spot

* Use route helper to build CRM URL
2024-07-12 12:01:59 +02:00

196 lines
4.8 KiB
Elixir

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