From a980dca4e584680829b767e53b2f51dcde9812c3 Mon Sep 17 00:00:00 2001 From: Harshad Sharma Date: Mon, 13 May 2024 15:29:38 +0530 Subject: [PATCH] Add mix release, commands to create user and reset password. --- lib/freedive/application.ex | 13 +++- lib/freedive/release.ex | 145 ++++++++++++++++++++++++++++++++++++ rel/overlays/bin/migrate | 5 ++ rel/overlays/bin/server | 5 ++ 4 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 lib/freedive/release.ex create mode 100755 rel/overlays/bin/migrate create mode 100755 rel/overlays/bin/server diff --git a/lib/freedive/application.ex b/lib/freedive/application.ex index 7058f32..d072ec9 100644 --- a/lib/freedive/application.ex +++ b/lib/freedive/application.ex @@ -2,17 +2,22 @@ defmodule Freedive.Application do # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @moduledoc false + @app :freedive use Application @impl true def start(_type, _args) do - children = [ + minimal = Application.get_env(@app, :minimal) + + app_minimal = [ FreediveWeb.Telemetry, Freedive.Repo, {Ecto.Migrator, - repos: Application.fetch_env!(:freedive, :ecto_repos), - skip: skip_migrations?()}, + repos: Application.fetch_env!(:freedive, :ecto_repos), skip: skip_migrations?()} + ] + + app_features = [ {DNSCluster, query: Application.get_env(:freedive, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: Freedive.PubSub}, # Start the Finch HTTP client for sending emails @@ -23,6 +28,8 @@ defmodule Freedive.Application do FreediveWeb.Endpoint ] + children = if minimal, do: app_minimal, else: app_minimal ++ app_features + # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Freedive.Supervisor] diff --git a/lib/freedive/release.ex b/lib/freedive/release.ex new file mode 100644 index 0000000..3dccc69 --- /dev/null +++ b/lib/freedive/release.ex @@ -0,0 +1,145 @@ +defmodule Freedive.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :freedive + require Logger + + def account_create do + start_app() + + email = Input.get_text("Enter email:") + password = Input.get_password("Enter password:") + confirm_password = Input.get_password("Confirm password:") + + if password != confirm_password do + Logger.error("Passwords do not match.") + exit(1) + end + + confirmed_at = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + + case Freedive.Accounts.register_user(%{ + email: email, + password: password, + confirmed_at: confirmed_at + }) do + {:ok, _user} -> + Logger.warning("User created successfully.") + + {:error, %Ecto.Changeset{errors: errors}} -> + Logger.error("Failed to create user") + Enum.each(errors, fn {key, message} -> Logger.error("#{key}: #{inspect(message)}") end) + end + end + + def password_reset do + start_app() + + email = Input.get_text("Enter email:") + user = Freedive.Accounts.get_user_by_email(email) + + case user do + nil -> + Logger.error("User not found.") + exit(1) + + _ -> + password = Input.get_password("Enter new password:") + confirm_password = Input.get_password("Confirm new password:") + + if password != confirm_password do + Logger.error("Passwords do not match.") + exit(1) + end + + case Freedive.Release.Account.update_user_password(user, password) do + {:ok, _user} -> + Logger.warning("Password updated successfully.") + + {:error, changeset} -> + Logger.error("Failed to update password") + + Enum.each(changeset.errors, fn {key, message} -> + Logger.error("#{key}: #{inspect(message)}") + end) + end + end + end + + def migrate do + load_app() + + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + load_app() + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp load_app do + Application.load(@app) + end + + defp start_app do + load_app() + Application.put_env(@app, :minimal, true) + Application.ensure_all_started(@app) + end +end + +defmodule Input do + def get_text(prompt) do + IO.write("#{prompt} ") + IO.gets("") |> String.trim() + end + + # Borrowed from Mix.Tasks.Hex.password_get/1 + def get_password(prompt) do + pid = spawn_link(fn -> loop(prompt) end) + ref = make_ref() + value = IO.gets("#{prompt} ") + + send(pid, {:done, self(), ref}) + receive do: ({:done, ^pid, ^ref} -> :ok) + + value |> String.trim() + end + + defp loop(prompt) do + receive do + {:done, parent, ref} -> + send(parent, {:done, self(), ref}) + IO.write(:standard_error, "\e[2K\r") + after + 1 -> + IO.write(:standard_error, "\e[2K\r#{prompt} ") + loop(prompt) + end + end +end + +defmodule Freedive.Release.Account do + def update_user_password(user, password) do + changeset = + user + |> Freedive.Accounts.User.password_changeset(%{password: password}) + + Ecto.Multi.new() + |> Ecto.Multi.update(:user, changeset) + |> Ecto.Multi.delete_all(:tokens, Freedive.Accounts.UserToken.by_user_and_contexts_query(user, :all)) + |> Freedive.Repo.transaction() + |> case do + {:ok, %{user: user}} -> {:ok, user} + {:error, :user, changeset, _} -> {:error, changeset} + end + end +end diff --git a/rel/overlays/bin/migrate b/rel/overlays/bin/migrate new file mode 100755 index 0000000..01342e1 --- /dev/null +++ b/rel/overlays/bin/migrate @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +exec ./freedive eval Freedive.Release.migrate diff --git a/rel/overlays/bin/server b/rel/overlays/bin/server new file mode 100755 index 0000000..1e189f2 --- /dev/null +++ b/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./freedive start