From 2e61ee0031081ec895c9955b93b7931e8abfdd95 Mon Sep 17 00:00:00 2001 From: Adam Piontek Date: Fri, 26 Mar 2021 18:55:17 -0400 Subject: [PATCH] working to get bones73k up to par with work done on shift73k --- .gitignore | 3 ++ config/config.exs | 2 + config/prod.secret.exs | 44 ---------------- lib/bones73k.ex | 5 ++ lib/bones73k/accounts/user.ex | 26 +++------- lib/bones73k/accounts/user_token.ex | 21 ++++---- lib/bones73k/util/dt.ex | 10 ---- .../live/user_management/index.ex | 51 ++++++++++++------- .../live/user_management/index.html.leex | 16 +++--- .../layout/navbar/_user_menu.html.eex | 2 +- .../templates/layout/root.html.leex | 2 +- mix.exs | 3 +- ...0200913000515_create_users_auth_tables.exs | 1 + .../20200913000905_add_role_to_users.exs | 21 -------- 14 files changed, 73 insertions(+), 134 deletions(-) delete mode 100644 config/prod.secret.exs delete mode 100644 lib/bones73k/util/dt.ex delete mode 100644 priv/repo/migrations/20200913000905_add_role_to_users.exs diff --git a/.gitignore b/.gitignore index d1239a6..75db7d3 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ npm-debug.log # for vscode elixir_ls extension files /.elixir_ls + +# dev +TODO.md diff --git a/config/config.exs b/config/config.exs index f78efc3..32bc914 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,6 +7,8 @@ # General application configuration use Mix.Config +config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase + config :bones73k, ecto_repos: [Bones73k.Repo] diff --git a/config/prod.secret.exs b/config/prod.secret.exs deleted file mode 100644 index 5eeea74..0000000 --- a/config/prod.secret.exs +++ /dev/null @@ -1,44 +0,0 @@ -# In this file, we load production configuration and secrets -# from environment variables. You can also hardcode secrets, -# although such is generally not recommended and you have to -# remember to add this file to your .gitignore. -use Mix.Config - -database_url = - System.get_env("DATABASE_URL") || - raise """ - environment variable DATABASE_URL is missing. - For example: ecto://USER:PASS@HOST/DATABASE - """ - -config :bones73k, Bones73k.Repo, - # ssl: true, - url: database_url, - pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") - -secret_key_base = - System.get_env("SECRET_KEY_BASE") || - raise """ - environment variable SECRET_KEY_BASE is missing. - You can generate one by calling: mix phx.gen.secret - """ - -config :bones73k, Bones73kWeb.Endpoint, - http: [ - port: String.to_integer(System.get_env("PORT") || "4000"), - transport_options: [socket_opts: [:inet6]] - ], - secret_key_base: secret_key_base - -# ## Using releases (Elixir v1.9+) -# -# If you are doing OTP releases, you need to instruct Phoenix -# to start each relevant endpoint: -# -# config :bones73k, Bones73kWeb.Endpoint, server: true -# -# Then you can assemble a release by calling `mix release`. -# See `mix help release` for more information. - -# Import extra secret stuff not to be included in git repo -import_config "really.secret.exs" diff --git a/lib/bones73k.ex b/lib/bones73k.ex index 01655ac..eb93652 100644 --- a/lib/bones73k.ex +++ b/lib/bones73k.ex @@ -6,4 +6,9 @@ defmodule Bones73k do Contexts are also responsible for managing your data, regardless if it comes from the database, an external API or others. """ + + @app_vars Application.get_env(:bones73k, :app_global_vars, time_zone: "America/New_York") + @app_time_zone @app_vars[:time_zone] + + def app_time_zone, do: @app_time_zone end diff --git a/lib/bones73k/accounts/user.ex b/lib/bones73k/accounts/user.ex index 63f9da3..bf1dfac 100644 --- a/lib/bones73k/accounts/user.ex +++ b/lib/bones73k/accounts/user.ex @@ -1,7 +1,6 @@ defmodule Bones73k.Accounts.User do use Ecto.Schema import Ecto.Changeset - import EctoEnum @roles [ user: "Basic user level", @@ -9,8 +8,6 @@ defmodule Bones73k.Accounts.User do admin: "Can delete users and change user roles" ] - defenum(RolesEnum, :role, Keyword.keys(@roles)) - @max_email 254 @min_password 6 @max_password 80 @@ -23,8 +20,8 @@ defmodule Bones73k.Accounts.User do field :password, :string, virtual: true field :hashed_password, :string field :confirmed_at, :naive_datetime + field :role, Ecto.Enum, values: Keyword.keys(@roles), default: :user - field :role, RolesEnum, default: :user timestamps() end @@ -73,21 +70,10 @@ defmodule Bones73k.Accounts.User do |> validate_password_not_required(opts) end - # def update_changeset_no_pw(user, attrs) do - # user - # |> cast(attrs, [:email, :role]) - # |> validate_role() - # |> validate_email() - # end - - defp role_validator(:role, role) do - (RolesEnum.valid_value?(role) && []) || [role: "invalid user role"] - end - defp validate_role(changeset) do changeset |> validate_required([:role]) - |> validate_change(:role, &role_validator/2) + |> validate_inclusion(:role, Keyword.keys(@roles), message: "invalid user role") end defp validate_email_format(changeset) do @@ -115,9 +101,11 @@ defmodule Bones73k.Accounts.User do defp validate_password_not_required(changeset, opts) do changeset |> validate_length(:password, min: @min_password, max: @max_password) - # |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character") - # |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character") - # |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character") + |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character") + |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character") + |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, + message: "at least one digit or punctuation character" + ) |> maybe_hash_password(opts) end diff --git a/lib/bones73k/accounts/user_token.ex b/lib/bones73k/accounts/user_token.ex index 7114f32..d84bfc1 100644 --- a/lib/bones73k/accounts/user_token.ex +++ b/lib/bones73k/accounts/user_token.ex @@ -15,9 +15,9 @@ defmodule Bones73k.Accounts.UserToken do @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "users_tokens" do - field :token, :binary - field :context, :string - field :sent_to, :string + field(:token, :binary) + field(:context, :string) + field(:sent_to, :string) belongs_to :user, Bones73k.Accounts.User timestamps(updated_at: false) @@ -40,10 +40,11 @@ defmodule Bones73k.Accounts.UserToken do """ def verify_session_token_query(token) do query = - from token in token_and_context_query(token, "session"), + from(token in token_and_context_query(token, "session"), join: user in assoc(token, :user), where: token.inserted_at > ago(@session_validity_in_days, "day"), select: user + ) {:ok, query} end @@ -85,10 +86,11 @@ defmodule Bones73k.Accounts.UserToken do days = days_for_context(context) query = - from token in token_and_context_query(hashed_token, context), + from(token in token_and_context_query(hashed_token, context), join: user in assoc(token, :user), where: token.inserted_at > ago(^days, "day") and token.sent_to == user.email, select: user + ) {:ok, query} @@ -111,8 +113,9 @@ defmodule Bones73k.Accounts.UserToken do hashed_token = :crypto.hash(@hash_algorithm, decoded_token) query = - from token in token_and_context_query(hashed_token, context), + from(token in token_and_context_query(hashed_token, context), where: token.inserted_at > ago(@change_email_validity_in_days, "day") + ) {:ok, query} @@ -125,17 +128,17 @@ defmodule Bones73k.Accounts.UserToken do Returns the given token with the given context. """ def token_and_context_query(token, context) do - from Bones73k.Accounts.UserToken, where: [token: ^token, context: ^context] + from(Bones73k.Accounts.UserToken, where: [token: ^token, context: ^context]) end @doc """ Gets all tokens for the given user for the given contexts. """ def user_and_contexts_query(user, :all) do - from t in Bones73k.Accounts.UserToken, where: t.user_id == ^user.id + from(t in Bones73k.Accounts.UserToken, where: t.user_id == ^user.id) end def user_and_contexts_query(user, [_ | _] = contexts) do - from t in Bones73k.Accounts.UserToken, where: t.user_id == ^user.id and t.context in ^contexts + from(t in Bones73k.Accounts.UserToken, where: t.user_id == ^user.id and t.context in ^contexts) end end diff --git a/lib/bones73k/util/dt.ex b/lib/bones73k/util/dt.ex deleted file mode 100644 index 24e26df..0000000 --- a/lib/bones73k/util/dt.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule Bones73k.Util.Dt do - @app_vars Application.get_env(:bones73k, :app_global_vars, time_zone: "America/New_York") - @time_zone @app_vars[:time_zone] - - def ndt_to_local(%NaiveDateTime{} = ndt), do: Timex.to_datetime(ndt, @time_zone) - - def format_dt_local(dt_local, fstr), do: Timex.format!(dt_local, fstr) - - def format_ndt(%NaiveDateTime{} = ndt, fstr), do: ndt |> ndt_to_local() |> format_dt_local(fstr) -end diff --git a/lib/bones73k_web/live/user_management/index.ex b/lib/bones73k_web/live/user_management/index.ex index d1da3b4..2db1efe 100644 --- a/lib/bones73k_web/live/user_management/index.ex +++ b/lib/bones73k_web/live/user_management/index.ex @@ -3,7 +3,6 @@ defmodule Bones73kWeb.UserManagementLive.Index do import Ecto.Query import Bones73kWeb.Pagination - import Bones73k.Util.Dt alias Bones73k.Repo alias Bones73k.Accounts @@ -27,9 +26,8 @@ defmodule Bones73kWeb.UserManagementLive.Index do if Roles.can?(current_user, user, live_action) do socket |> assign(:query, query_map(params)) - |> assign_modal_return_to() + |> assign_modal_close_handlers() |> assign(:delete_user, nil) - |> assign(:page, nil) |> request_page_query() |> apply_action(socket.assigns.live_action, params) |> live_noreply() @@ -64,9 +62,9 @@ defmodule Bones73kWeb.UserManagementLive.Index do |> assign(:user, Accounts.get_user!(id)) end - def assign_modal_return_to(%{assigns: %{query: query}} = socket) do + defp assign_modal_close_handlers(%{assigns: %{query: query}} = socket) do to = Routes.user_management_index_path(socket, :index, Enum.into(query, [])) - assign(socket, :modal_return_to, to) + assign(socket, modal_return_to: to, modal_close_action: :return) end defp user_from_params(params) @@ -97,7 +95,10 @@ defmodule Bones73kWeb.UserManagementLive.Index do @impl true def handle_event("delete-modal", %{"id" => id}, socket) do - {:noreply, assign(socket, :delete_user, Accounts.get_user(id))} + socket + |> assign(:modal_close_action, :delete_user) + |> assign(:delete_user, Accounts.get_user!(id)) + |> live_noreply() end @impl true @@ -113,17 +114,13 @@ defmodule Bones73kWeb.UserManagementLive.Index do end @impl true - def handle_event( - "sort-change", - %{"sort_by" => column} = params, - %{assigns: %{query: query}} = socket - ) do - (column == query.sort_by && - send( - self(), - {:query_update, %{"sort_order" => (query.sort_order == "asc" && "desc") || "asc"}} - )) || + def handle_event("sort-change", %{"sort_by" => column} = params, socket) do + if column == socket.assigns.query.sort_by do + order = (socket.assigns.query.sort_order == "asc" && "desc") || "asc" + send(self(), {:query_update, %{"sort_order" => order}}) + else send(self(), {:query_update, Map.put(params, "sort_order", "asc")}) + end {:noreply, socket} end @@ -168,8 +165,20 @@ defmodule Bones73kWeb.UserManagementLive.Index do end @impl true - def handle_info({:close_modal, _}, %{assigns: %{modal_return_to: to}} = socket) do - socket |> copy_flash() |> push_patch(to: to) |> live_noreply() + def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: :return}} = socket) do + socket + |> copy_flash() + |> push_patch(to: socket.assigns.modal_return_to) + |> live_noreply() + end + + @impl true + def handle_info({:close_modal, _}, %{assigns: %{modal_close_action: assign_key}} = socket) do + socket + |> assign(assign_key, nil) + |> assign_modal_close_handlers() + |> request_page_query() + |> live_noreply() end @impl true @@ -187,5 +196,9 @@ defmodule Bones73kWeb.UserManagementLive.Index do ] end - def dt_out(ndt), do: format_ndt(ndt, "{YYYY} {Mshort} {0D}, {h12}:{0m} {am}") + def dt_out(ndt) do + ndt + |> DateTime.from_naive!(Bones73k.app_time_zone()) + |> Calendar.strftime("%Y %b %-d, %-I:%M %p") + end end diff --git a/lib/bones73k_web/live/user_management/index.html.leex b/lib/bones73k_web/live/user_management/index.html.leex index cb38f2d..8e5d4ea 100644 --- a/lib/bones73k_web/live/user_management/index.html.leex +++ b/lib/bones73k_web/live/user_management/index.html.leex @@ -11,8 +11,8 @@ <%= live_modal @socket, Bones73kWeb.UserManagement.DeleteComponent, id: @delete_user.id, title: "Delete User", - delete_user: @delete_user, - current_user: @current_user %> + delete_user: @delete_user + %> <% end %> @@ -92,20 +92,20 @@
<%= user.confirmed_at && "Yes" || "No" %> - > + disabled>
<%= if Roles.can?(@current_user, user, :edit) do %> <%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-primary btn-sm text-nowrap" do %> - <%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %> + <%= icon_div @socket, "bi-pencil", [class: "icon baseline"] %> Edit <% end %> <% end %> <%= if Roles.can?(@current_user, user, :delete) do %> <% end %> @@ -174,20 +174,20 @@ <%= dt_out(user.inserted_at) %> <%= user.confirmed_at && "Confirmed" || "Not confirmed" %> - > + disabled> <%= if Roles.can?(@current_user, user, :edit) do %> <%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-outline-primary btn-sm text-nowrap" do %> - <%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %> + <%= icon_div @socket, "bi-pencil", [class: "icon baseline"] %> Edit <% end %> <% end %> <%= if Roles.can?(@current_user, user, :delete) do %> <% end %> diff --git a/lib/bones73k_web/templates/layout/navbar/_user_menu.html.eex b/lib/bones73k_web/templates/layout/navbar/_user_menu.html.eex index 42de344..89b81f5 100644 --- a/lib/bones73k_web/templates/layout/navbar/_user_menu.html.eex +++ b/lib/bones73k_web/templates/layout/navbar/_user_menu.html.eex @@ -1,7 +1,7 @@ <%= if !@current_user do %> <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light") do %> - <%= icon_div @conn, "bi-door-open", [class: "icon baseline", style: "margin-right:0.125rem;"] %> + <%= icon_div @conn, "bi-door-open", [class: "icon baseline"] %> Log in <% end %> diff --git a/lib/bones73k_web/templates/layout/root.html.leex b/lib/bones73k_web/templates/layout/root.html.leex index 4cfa731..093f390 100644 --- a/lib/bones73k_web/templates/layout/root.html.leex +++ b/lib/bones73k_web/templates/layout/root.html.leex @@ -5,7 +5,7 @@ <%= csrf_meta_tag() %> - <%= live_title_tag assigns[:page_title] || "Bones73k", suffix: " · Phoenix Framework" %> + <%= live_title_tag assigns[:page_title] || "", suffix: assigns[:page_title] && " · Bones73k" || "Bones73k" %> "/> diff --git a/mix.exs b/mix.exs index 2a939c4..2e62792 100644 --- a/mix.exs +++ b/mix.exs @@ -48,11 +48,10 @@ defmodule Bones73k.MixProject do {:gettext, "~> 0.11"}, {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, - {:ecto_enum, "~> 1.4"}, {:bamboo, "~> 2.0"}, {:bamboo_smtp, "~> 4.0"}, {:scrivener_ecto, "~> 2.0"}, - {:timex, "~> 3.5"} + {:tzdata, "~> 1.1"} ] end diff --git a/priv/repo/migrations/20200913000515_create_users_auth_tables.exs b/priv/repo/migrations/20200913000515_create_users_auth_tables.exs index efc5f61..4184e91 100644 --- a/priv/repo/migrations/20200913000515_create_users_auth_tables.exs +++ b/priv/repo/migrations/20200913000515_create_users_auth_tables.exs @@ -8,6 +8,7 @@ defmodule Bones73k.Repo.Migrations.CreateUsersAuthTables do add(:id, :binary_id, primary_key: true) add(:email, :citext, null: false) add(:hashed_password, :string, null: false) + add(:role, :string, null: false) add(:confirmed_at, :naive_datetime) timestamps() end diff --git a/priv/repo/migrations/20200913000905_add_role_to_users.exs b/priv/repo/migrations/20200913000905_add_role_to_users.exs deleted file mode 100644 index 68b32a6..0000000 --- a/priv/repo/migrations/20200913000905_add_role_to_users.exs +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Bones73k.Repo.Migrations.AddRoleToUsers do - use Ecto.Migration - - alias Bones73k.Accounts.User.RolesEnum - - def up do - RolesEnum.create_type() - - alter table(:users) do - add(:role, RolesEnum.type(), null: false) - end - end - - def down do - alter table(:users) do - remove(:role) - end - - RolesEnum.drop_type() - end -end