From f27df8d676b17aca86d6b447c230c3df4fc744d6 Mon Sep 17 00:00:00 2001 From: Adam Piontek <adam@73k.us> Date: Sun, 14 Aug 2022 09:14:42 -0400 Subject: [PATCH] implemented optional 'allow_registration' config, with first registered user being pre-confirmed Admin, registration unavailable after that point if allow_registration: :false --- assets/js/_hamburger-helper.js | 20 +- config/config.exs | 3 +- lib/shift73k/accounts.ex | 7 + lib/shift73k_web/live/user/registration.ex | 34 ++- .../live/user/registration.html.heex | 4 +- lib/shift73k_web/live/user/reset_password.ex | 5 + .../live/user/reset_password.html.heex | 4 +- .../plugs/ensure_allow_registration_plug.ex | 35 +++ lib/shift73k_web/plugs/ensure_role_plug.ex | 3 +- .../plugs/ensure_user_exist_plug.ex | 30 +++ lib/shift73k_web/router.ex | 91 ++++--- .../templates/layout/_navbar.html.heex | 92 +++---- .../layout/navbar/_nouser_menu.html.heex | 23 ++ .../layout/navbar/_user_menu.html.heex | 2 +- .../templates/user_confirmation/new.html.heex | 3 + .../user_reset_password/new.html.heex | 4 +- .../templates/user_session/new.html.heex | 4 +- lib/shift73k_web/views/layout_view.ex | 7 +- .../views/user_confirmation_view.ex | 5 + .../views/user_reset_password_view.ex | 5 + lib/shift73k_web/views/user_session_view.ex | 5 + priv/repo/seeds.exs | 253 +++++++++--------- 22 files changed, 395 insertions(+), 244 deletions(-) create mode 100644 lib/shift73k_web/plugs/ensure_allow_registration_plug.ex create mode 100644 lib/shift73k_web/plugs/ensure_user_exist_plug.ex create mode 100644 lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex diff --git a/assets/js/_hamburger-helper.js b/assets/js/_hamburger-helper.js index a03c992c..c9c4f479 100644 --- a/assets/js/_hamburger-helper.js +++ b/assets/js/_hamburger-helper.js @@ -1,12 +1,14 @@ const togglerBtn = document.getElementById('navbarSupportedContentToggler'); const navbarContent = document.getElementById('navbarSupportedContent'); -navbarContent.addEventListener('show.bs.collapse', () => { - console.log('opening navbar content'); - togglerBtn.classList.toggle('is-active'); -}); - -navbarContent.addEventListener('hide.bs.collapse', () => { - console.log('closing navbar content'); - togglerBtn.classList.toggle('is-active'); -}); +if (navbarContent != null) { + navbarContent.addEventListener('show.bs.collapse', () => { + console.log('opening navbar content'); + togglerBtn.classList.toggle('is-active'); + }); + + navbarContent.addEventListener('hide.bs.collapse', () => { + console.log('closing navbar content'); + togglerBtn.classList.toggle('is-active'); + }); +} diff --git a/config/config.exs b/config/config.exs index ad34667d..5d43f0c0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -16,7 +16,8 @@ config :shift73k, config :shift73k, :app_global_vars, time_zone: "America/New_York", mailer_reply_to: "reply_to@example.com", - mailer_from: "app_name@example.com" + mailer_from: "app_name@example.com", + allow_registration: :true # Configures the endpoint config :shift73k, Shift73kWeb.Endpoint, diff --git a/lib/shift73k/accounts.ex b/lib/shift73k/accounts.ex index 199a52b3..1ca0eeef 100644 --- a/lib/shift73k/accounts.ex +++ b/lib/shift73k/accounts.ex @@ -108,6 +108,13 @@ defmodule Shift73k.Accounts do """ def register_user(attrs) do + # If attrs has atom keys, convert to string + # If attrs don't include role, put default role + attrs = + attrs + |> Map.new(fn {k, v} -> {to_string(k), v} end) + |> Map.put_new("role", registration_role()) + %User{} |> User.registration_changeset(attrs) |> Repo.insert() diff --git a/lib/shift73k_web/live/user/registration.ex b/lib/shift73k_web/live/user/registration.ex index 8b86b0fe..fc0436a3 100644 --- a/lib/shift73k_web/live/user/registration.ex +++ b/lib/shift73k_web/live/user/registration.ex @@ -1,6 +1,6 @@ defmodule Shift73kWeb.UserLive.Registration do use Shift73kWeb, :live_view - + alias Shift73k.Repo alias Shift73k.Accounts alias Shift73k.Accounts.User @@ -20,9 +20,7 @@ defmodule Shift73kWeb.UserLive.Registration do user_id: nil, user_return_to: Map.get(session, "user_return_to", "/"), messages: [ - success: "Welcome! Your new account has been created, and you've been logged in.", - info: - "Some features may be unavailable until you confirm your email address. Check your inbox for instructions." + success: "Welcome! Your new account has been created, and you've been logged in." ] } end @@ -35,19 +33,33 @@ defmodule Shift73kWeb.UserLive.Registration do @impl true def handle_event("save", %{"user" => user_params}, socket) do + is_first_user = !Repo.exists?(User) user_params - |> Map.put("role", Accounts.registration_role()) |> Accounts.register_user() |> case do {:ok, user} -> - {:ok, _, %Swoosh.Email{} = _captured_email} = - Accounts.deliver_user_confirmation_instructions( - user, - &Routes.user_confirmation_url(socket, :confirm, &1) - ) + # If this is the first user, we just confirm them + if is_first_user do + user |> User.confirm_changeset() |> Repo.update() + else + # Otherwise, all new users require email confirmation so we wend instructions + {:ok, _, %Swoosh.Email{} = _captured_email} = + Accounts.deliver_user_confirmation_instructions( + user, + &Routes.user_confirmation_url(socket, :confirm, &1) + ) + end + + login_params = + if is_first_user do + socket.assigns.login_params + else + put_in(socket.assigns.login_params, [:messages, :info], "Some features may be unavailable until you confirm your email address. Check your inbox for instructions.") + end + |> put_in([:user_id], user.id) socket - |> assign(login_params: %{socket.assigns.login_params | user_id: user.id}) + |> assign(login_params: login_params) |> assign(trigger_submit: true) |> live_noreply() diff --git a/lib/shift73k_web/live/user/registration.html.heex b/lib/shift73k_web/live/user/registration.html.heex index 41f9a11d..ca00d39c 100644 --- a/lib/shift73k_web/live/user/registration.html.heex +++ b/lib/shift73k_web/live/user/registration.html.heex @@ -6,7 +6,7 @@ </h2> <p class="lead">Create an account to manage your work shifts with us.</p> - <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, novalidate: true, id: "reg_form"], fn f -> %> + <.form let={f} for={@changeset} phx-change="validate" phx-submit="save" novalidate id="reg_form"> <%= label f, :email, class: "form-label" %> <div class="inner-addon left-addon mb-3" phx-feedback-for={input_id(f, :email)}> @@ -45,7 +45,7 @@ %> </div> - <% end %> + </.form> <p> <%= link "Log in", to: Routes.user_session_path(@socket, :new) %> | diff --git a/lib/shift73k_web/live/user/reset_password.ex b/lib/shift73k_web/live/user/reset_password.ex index 2c2b801c..2aa5856c 100644 --- a/lib/shift73k_web/live/user/reset_password.ex +++ b/lib/shift73k_web/live/user/reset_password.ex @@ -4,6 +4,9 @@ defmodule Shift73kWeb.UserLive.ResetPassword do alias Shift73k.Accounts alias Shift73k.Accounts.User + @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true) + @app_allow_registration @app_vars[:allow_registration] + @impl true def mount(_params, session, socket) do user = Accounts.get_user!(session["user_id"]) @@ -37,4 +40,6 @@ defmodule Shift73kWeb.UserLive.ResetPassword do |> assign(changeset: changeset)} end end + + def allow_registration, do: @app_allow_registration end diff --git a/lib/shift73k_web/live/user/reset_password.html.heex b/lib/shift73k_web/live/user/reset_password.html.heex index f7761c69..0abfcea5 100644 --- a/lib/shift73k_web/live/user/reset_password.html.heex +++ b/lib/shift73k_web/live/user/reset_password.html.heex @@ -45,7 +45,9 @@ <% end %> <p class="mt-3 is-pulled-right"> - <%= link "Register", to: Routes.user_registration_path(@socket, :new) %> | + <%= if allow_registration() do %> + <%= link "Register", to: Routes.user_registration_path(@socket, :new) %> | + <% end %> <%= link "Log in", to: Routes.user_session_path(@socket, :new) %> </p> diff --git a/lib/shift73k_web/plugs/ensure_allow_registration_plug.ex b/lib/shift73k_web/plugs/ensure_allow_registration_plug.ex new file mode 100644 index 00000000..7dfa0e96 --- /dev/null +++ b/lib/shift73k_web/plugs/ensure_allow_registration_plug.ex @@ -0,0 +1,35 @@ +defmodule Shift73kWeb.EnsureAllowRegistrationPlug do + @moduledoc """ + This plug ensures that there is at least one known User. + """ + import Plug.Conn + import Phoenix.Controller + + alias Shift73k.Repo + alias Shift73k.Accounts.User + + @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true) + @app_allow_registration @app_vars[:allow_registration] + + @doc false + @spec init(any()) :: any() + def init(config), do: config + + @doc false + @spec call(Conn.t(), atom() | [atom()]) :: Conn.t() + def call(conn, _opts) do + # If there aren't even any users, or registration is allowed + if !Repo.exists?(User) || @app_allow_registration do + # We will allow registration + conn + else + # Otherwise, + # if app is configured to not allow registration, + # and there is a user, + # then we redirect to root URL + conn + |> redirect(to: "/") + |> halt() + end + end +end diff --git a/lib/shift73k_web/plugs/ensure_role_plug.ex b/lib/shift73k_web/plugs/ensure_role_plug.ex index 9c604e11..e8ac84d5 100644 --- a/lib/shift73k_web/plugs/ensure_role_plug.ex +++ b/lib/shift73k_web/plugs/ensure_role_plug.ex @@ -27,8 +27,7 @@ defmodule Shift73kWeb.EnsureRolePlug do def call(conn, roles) do user_token = get_session(conn, :user_token) - (user_token && - Accounts.get_user_by_session_token(user_token)) + (user_token && Accounts.get_user_by_session_token(user_token)) |> has_role?(roles) |> maybe_halt(conn) end diff --git a/lib/shift73k_web/plugs/ensure_user_exist_plug.ex b/lib/shift73k_web/plugs/ensure_user_exist_plug.ex new file mode 100644 index 00000000..3856fec9 --- /dev/null +++ b/lib/shift73k_web/plugs/ensure_user_exist_plug.ex @@ -0,0 +1,30 @@ +defmodule Shift73kWeb.EnsureUserExistPlug do + @moduledoc """ + This plug ensures that there is at least one known User. + """ + import Plug.Conn + import Phoenix.Controller + + alias Shift73k.Repo + alias Shift73k.Accounts.User + alias Shift73kWeb.Router.Helpers, as: Routes + + @doc false + @spec init(any()) :: any() + def init(config), do: config + + @doc false + @spec call(Conn.t(), atom() | [atom()]) :: Conn.t() + def call(conn, _opts) do + # If there aren't even any users, + if !Repo.exists?(User) do + # We're just going to redirect to registration + conn + |> redirect(to: Routes.user_registration_path(conn, :new)) + |> halt() + else + # Otherwise we proceed as normal + conn + end + end +end diff --git a/lib/shift73k_web/router.ex b/lib/shift73k_web/router.ex index b45acda4..7ed69670 100644 --- a/lib/shift73k_web/router.ex +++ b/lib/shift73k_web/router.ex @@ -2,27 +2,37 @@ defmodule Shift73kWeb.Router do use Shift73kWeb, :router import Shift73kWeb.UserAuth alias Shift73kWeb.EnsureRolePlug + alias Shift73kWeb.EnsureUserExistPlug + alias Shift73kWeb.EnsureAllowRegistrationPlug pipeline :browser do - plug(:accepts, ["html"]) - plug(:fetch_session) - plug(:fetch_live_flash) - plug(:put_root_layout, {Shift73kWeb.LayoutView, :root}) - plug(:protect_from_forgery) - plug(:put_secure_browser_headers) - plug(:fetch_current_user) + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, {Shift73kWeb.LayoutView, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + plug :fetch_current_user end - pipeline :user do - plug(EnsureRolePlug, [:admin, :manager, :user]) + pipeline :ensure_role_user do + plug EnsureRolePlug, [:admin, :manager, :user] end - pipeline :manager do - plug(EnsureRolePlug, [:admin, :manager]) + pipeline :ensure_user_exist do + plug EnsureUserExistPlug end - pipeline :admin do - plug(EnsureRolePlug, :admin) + pipeline :ensure_allow_registration do + plug EnsureAllowRegistrationPlug + end + + pipeline :ensure_role_manager do + plug EnsureRolePlug, [:admin, :manager] + end + + pipeline :ensure_role_admin do + plug EnsureRolePlug, :admin end # Enables the Swoosh mailbox preview in development. @@ -38,49 +48,54 @@ defmodule Shift73kWeb.Router do end scope "/", Shift73kWeb do - pipe_through([:browser]) + pipe_through([:browser, :ensure_user_exist]) - get("/", Redirector, to: "/assign") + get "/", Redirector, to: "/assign" end scope "/", Shift73kWeb do - pipe_through([:browser, :redirect_if_user_is_authenticated]) + pipe_through [:browser, :redirect_if_user_is_authenticated, :ensure_allow_registration] + + get "/users/register", UserRegistrationController, :new + end + + scope "/", Shift73kWeb do + pipe_through [:browser, :redirect_if_user_is_authenticated, :ensure_user_exist] # session routes, irrelevant if user is authenticated - get("/users/register", UserRegistrationController, :new) - get("/users/log_in", UserSessionController, :new) - post("/users/log_in", UserSessionController, :create) - get("/users/reset_password", UserResetPasswordController, :new) - post("/users/reset_password", UserResetPasswordController, :create) - get("/users/reset_password/:token", UserResetPasswordController, :edit) + get "/users/log_in", UserSessionController, :new + post "/users/log_in", UserSessionController, :create + get "/users/reset_password", UserResetPasswordController, :new + post "/users/reset_password", UserResetPasswordController, :create + get "/users/reset_password/:token", UserResetPasswordController, :edit end scope "/", Shift73kWeb do - pipe_through([:browser, :require_authenticated_user]) + pipe_through [:browser, :require_authenticated_user] # user settings (change email, password, calendar week start, etc) - live("/users/settings", UserLive.Settings, :edit) + live "/users/settings", UserLive.Settings, :edit # confirm email by token - get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email) + get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email end scope "/", Shift73kWeb do - pipe_through([:browser]) + pipe_through [:browser, :ensure_user_exist] # session paths - delete("/users/log_out", UserSessionController, :delete) - get("/users/force_logout", UserSessionController, :force_logout) - get("/users/confirm", UserConfirmationController, :new) - post("/users/confirm", UserConfirmationController, :create) - get("/users/confirm/:token", UserConfirmationController, :confirm) + delete "/users/log_out", UserSessionController, :delete + get "/users/force_logout", UserSessionController, :force_logout + get "/users/confirm", UserConfirmationController, :new + post "/users/confirm", UserConfirmationController, :create + get "/users/confirm/:token", UserConfirmationController, :confirm # ics/ical route for user's shifts - get("/ics/:slug", UserShiftsIcsController, :index) + get "/ics/:slug", UserShiftsIcsController, :index end scope "/", Shift73kWeb do - pipe_through([:browser, :require_authenticated_user, :user]) + pipe_through [:browser, :require_authenticated_user, :ensure_role_user] live "/templates", ShiftTemplateLive.Index, :index live "/templates/new", ShiftTemplateLive.Index, :new @@ -98,16 +113,16 @@ defmodule Shift73kWeb.Router do end # scope "/", Shift73kWeb do - # pipe_through([:browser, :require_authenticated_user, :admin]) + # pipe_through([:browser, :require_authenticated_user, :ensure_role_admin]) # end # Users Management scope "/users", Shift73kWeb do - pipe_through([:browser, :require_authenticated_user, :manager, :require_email_confirmed]) + pipe_through [:browser, :require_authenticated_user, :ensure_role_manager, :require_email_confirmed] - live("/", UserManagementLive.Index, :index) - live("/new", UserManagementLive.Index, :new) - live("/edit/:id", UserManagementLive.Index, :edit) + live "/", UserManagementLive.Index, :index + live "/new", UserManagementLive.Index, :new + live "/edit/:id", UserManagementLive.Index, :edit # resources "/", UserManagementController, only: [:new, :create, :edit, :update] end end diff --git a/lib/shift73k_web/templates/layout/_navbar.html.heex b/lib/shift73k_web/templates/layout/_navbar.html.heex index a98856e6..4c81942d 100644 --- a/lib/shift73k_web/templates/layout/_navbar.html.heex +++ b/lib/shift73k_web/templates/layout/_navbar.html.heex @@ -8,79 +8,63 @@ <% end %> </h1> - <%= if @current_user do %> + <%# If there's a current user, + OR if there are users & we allow registration, + THEN we will show a full menu configuration %> + + <%= if @current_user || (Repo.exists?(User) && allow_registration()) do %> + <button class="hamburger hamburger--squeeze collapsed navbar-toggler" id="navbarSupportedContentToggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="hamburger-box d-flex"> <span class="hamburger-inner"></span> </span> </button> - <% else %> - <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-block d-lg-none") do %> - <i class="bi bi-door-open me-1"></i> Log in - <% end %> - <% end %> - <div class="collapse navbar-collapse" id="navbarSupportedContent"> + + <div class="collapse navbar-collapse" id="navbarSupportedContent"> - <%# nav LEFT items %> - <ul class="navbar-nav me-auto mb-2 mb-lg-0"> + <%# nav LEFT items %> + <ul class="navbar-nav me-auto mb-2 mb-lg-0"> - <%#= if @current_user do %> - <%# <li class="nav-item"> %> - <%#= link nav_link_opts(@conn, to: Routes.shift_template_index_path(@conn, :index), class: "nav-link") do %> - <%#= icon_div @conn, "bi-clock-history", [class: "icon baseline me-1"] %> - <%# Templates %> - <%# end %> - <%# </li> %> - <%# end %> + </ul> - <%# normal navbar link example %> - <%# <li class="nav-item"> %> - <%#= link "Properties", nav_link_opts(@conn, to: Routes.property_index_path(@conn, :index), class: "nav-link") %> - <%# </li> %> - <%# ACTIVE page link example %> - <%# <li class="nav-item"> - <a class="nav-link active" aria-current="page" href="#">Home</a> - </li> %> + <%# nav RIGHT items %> + <ul class="navbar-nav"> - <%# DISABLED page link example %> - <%# <li class="nav-item"> - <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> - </li> %> + <%= if @current_user do %> - <%# normal dropdown menu example %> - <%# <li class="nav-item dropdown"> - <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownExample" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</a> - <ul class="dropdown-menu" aria-labelledby="navbarDropdownExample"> - <li><a class="dropdown-item" href="#">Action</a></li> - <li><a class="dropdown-item" href="#">Another action</a></li> - <li><hr class="dropdown-divider"></li> - <li><a class="dropdown-item" href="#">Something else here</a></li> - </ul> - </li> %> + <%= render "navbar/_shifts_menu.html", assigns %> - </ul> + <%= render "navbar/_user_menu.html", assigns %> + + <% else %> - <%# nav RIGHT items %> - <ul class="navbar-nav"> + <%= render "navbar/_nouser_menu.html", assigns %> - <%= if @current_user do %> - - <%= render "navbar/_shifts_menu.html", assigns %> - - <%= render "navbar/_user_menu.html", assigns %> - - <% else %> - - <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light d-none d-lg-block") do %> - <i class="bi bi-door-open me-1"></i> Log in <% end %> + </ul> + + </div> + + <%# If there's no current user, + AND: + There are no users -- [REGISTER] + OR no registration allowed -- [LOG IN] %> + <%= else %> + + <%= if !Repo.exists?(User) || allow_registration() do %> + <%= link nav_link_opts(@conn, to: Routes.user_registration_path(@conn, :new), class: "btn btn-outline-light") do %> + <i class="bi bi-journal-plus"></i> Register <% end %> + <% else %> + <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "btn btn-outline-light") do %> + <i class="bi bi-door-open"></i> Log in + <% end %> + <% end %> - </ul> + <% end %> - </div> </div> </nav> diff --git a/lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex b/lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex new file mode 100644 index 00000000..59203835 --- /dev/null +++ b/lib/shift73k_web/templates/layout/navbar/_nouser_menu.html.heex @@ -0,0 +1,23 @@ +<li class="nav-item dropdown"> + + <a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false"> + <i class="bi bi-person-circle me-1"></i> Hello? + </a> + + <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownUserMenu"> + + <li> + <%= link nav_link_opts(@conn, to: Routes.user_registration_path(@conn, :new), class: "dropdown-item") do %> + <i class="bi bi-journal-plus me-1"></i> Register + <% end %> + </li> + + <li> + <%= link nav_link_opts(@conn, to: Routes.user_session_path(@conn, :new), class: "dropdown-item") do %> + <i class="bi bi-door-open me-1"></i> Log in + <% end %> + </li> + + </ul> + +</li> diff --git a/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex b/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex index c74a7324..191b291b 100644 --- a/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex +++ b/lib/shift73k_web/templates/layout/navbar/_user_menu.html.heex @@ -1,7 +1,7 @@ <li class="nav-item dropdown"> <a href="#" class="nav-link dropdown-toggle" id="navbarDropdownUserMenu" data-bs-toggle="dropdown" aria-expanded="false"> - <i class="bi bi-person-circle me-1"></i> <%= @current_user && "Hello!" || "Hello?" %> + <i class="bi bi-person-circle me-1"></i> Hello! </a> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownUserMenu"> diff --git a/lib/shift73k_web/templates/user_confirmation/new.html.heex b/lib/shift73k_web/templates/user_confirmation/new.html.heex index 1155cd44..35e22e76 100644 --- a/lib/shift73k_web/templates/user_confirmation/new.html.heex +++ b/lib/shift73k_web/templates/user_confirmation/new.html.heex @@ -29,6 +29,9 @@ <% end %> <p> + <%= if allow_registration() do %> + <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> | + <% end %> <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> | <%= link "Log in", to: Routes.user_session_path(@conn, :new) %> </p> diff --git a/lib/shift73k_web/templates/user_reset_password/new.html.heex b/lib/shift73k_web/templates/user_reset_password/new.html.heex index 8bcab246..f5be0f7f 100644 --- a/lib/shift73k_web/templates/user_reset_password/new.html.heex +++ b/lib/shift73k_web/templates/user_reset_password/new.html.heex @@ -27,7 +27,9 @@ <% end %> <p> - <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> | + <%= if allow_registration() do %> + <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> | + <% end %> <%= link "Log in", to: Routes.user_session_path(@conn, :new) %> </p> diff --git a/lib/shift73k_web/templates/user_session/new.html.heex b/lib/shift73k_web/templates/user_session/new.html.heex index 06e3f943..7d934da1 100644 --- a/lib/shift73k_web/templates/user_session/new.html.heex +++ b/lib/shift73k_web/templates/user_session/new.html.heex @@ -49,7 +49,9 @@ <% end %> <p> - <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> | + <%= if allow_registration() do %> + <%= link "Register", to: Routes.user_registration_path(@conn, :new) %> | + <% end %> <%= link "Forgot your password?", to: Routes.user_reset_password_path(@conn, :new) %> </p> diff --git a/lib/shift73k_web/views/layout_view.ex b/lib/shift73k_web/views/layout_view.ex index 1c857430..b35f4aaa 100644 --- a/lib/shift73k_web/views/layout_view.ex +++ b/lib/shift73k_web/views/layout_view.ex @@ -1,9 +1,12 @@ defmodule Shift73kWeb.LayoutView do use Shift73kWeb, :view - + alias Shift73k.Repo alias Shift73k.Accounts.User alias Shift73kWeb.Roles + @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true) + @app_allow_registration @app_vars[:allow_registration] + # With a Vite.js-based workflow, we will import different asset files in development # and in production builds. Therefore, we will need a way to conditionally render # <script> tags based on Mix environment. However, since Mix is not available in @@ -11,6 +14,8 @@ defmodule Shift73kWeb.LayoutView do @env Mix.env() # remember value at compile time def dev_env?, do: @env == :dev + def allow_registration, do: @app_allow_registration + def nav_link_opts(conn, opts) do case Keyword.get(opts, :to) == Phoenix.Controller.current_path(conn) do false -> opts diff --git a/lib/shift73k_web/views/user_confirmation_view.ex b/lib/shift73k_web/views/user_confirmation_view.ex index 0c02ca8f..93131825 100644 --- a/lib/shift73k_web/views/user_confirmation_view.ex +++ b/lib/shift73k_web/views/user_confirmation_view.ex @@ -1,4 +1,9 @@ defmodule Shift73kWeb.UserConfirmationView do use Shift73kWeb, :view alias Shift73k.Accounts.User + + @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true) + @app_allow_registration @app_vars[:allow_registration] + + def allow_registration, do: @app_allow_registration end diff --git a/lib/shift73k_web/views/user_reset_password_view.ex b/lib/shift73k_web/views/user_reset_password_view.ex index 940a55bb..3d134259 100644 --- a/lib/shift73k_web/views/user_reset_password_view.ex +++ b/lib/shift73k_web/views/user_reset_password_view.ex @@ -1,4 +1,9 @@ defmodule Shift73kWeb.UserResetPasswordView do use Shift73kWeb, :view alias Shift73k.Accounts.User + + @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true) + @app_allow_registration @app_vars[:allow_registration] + + def allow_registration, do: @app_allow_registration end diff --git a/lib/shift73k_web/views/user_session_view.ex b/lib/shift73k_web/views/user_session_view.ex index 32055191..9c2fe576 100644 --- a/lib/shift73k_web/views/user_session_view.ex +++ b/lib/shift73k_web/views/user_session_view.ex @@ -1,4 +1,9 @@ defmodule Shift73kWeb.UserSessionView do use Shift73kWeb, :view alias Shift73k.Accounts.User + + @app_vars Application.compile_env(:shift73k, :app_global_vars, allow_registration: :true) + @app_allow_registration @app_vars[:allow_registration] + + def allow_registration, do: @app_allow_registration end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 6c143b84..116aff2b 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -14,141 +14,150 @@ alias Shift73k.Repo alias Shift73k.Accounts alias Shift73k.Accounts.User -############################################################################ -## INSERTING MOCK USER DATA -{:ok, _admin} = - Accounts.register_user(%{ - email: "admin@company.com", - password: "123456789abC", - password_confirmation: "123456789abC", - role: Accounts.registration_role() - }) +if Mix.env() == :dev do -{:ok, _user_1} = - Accounts.register_user(%{ - email: "user1@company.com", - password: "123456789abC", - password_confirmation: "123456789abC", - role: Accounts.registration_role() - }) + if System.get_env("ECTO_SEED_DB") do -{:ok, _user_2} = - Accounts.register_user(%{ - email: "user2@company.com", - password: "123456789abC", - password_confirmation: "123456789abC", - role: Accounts.registration_role() - }) + ############################################################################ + ## INSERTING MOCK USER DATA -# if Mix.env() == :dev do -this_path = Path.dirname(__ENV__.file) -users_json = Path.join(this_path, "MOCK_DATA_users.json") + {:ok, _admin} = + Accounts.register_user(%{ + email: "admin@company.com", + password: "123456789abC", + password_confirmation: "123456789abC", + role: Accounts.registration_role() + }) -count_to_take = 15 + {:ok, _user_1} = + Accounts.register_user(%{ + email: "user1@company.com", + password: "123456789abC", + password_confirmation: "123456789abC", + role: Accounts.registration_role() + }) -mock_users = users_json |> File.read!() |> Jason.decode!() |> Enum.take_random(count_to_take) + {:ok, _user_2} = + Accounts.register_user(%{ + email: "user2@company.com", + password: "123456789abC", + password_confirmation: "123456789abC", + role: Accounts.registration_role() + }) -extra_mock_users = ~s([ - {"email":"adam@73k.us","password":"adamadamA1","role":"admin","inserted_at":"2018-12-14T01:01:01Z","confirmed_at":true}, - {"email":"kat@73k.us","password":"katkatA1","role":"manager","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":true}, - {"email":"babka@73k.us","password":"Babka2020","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false}, - {"email":"malcolm@73k.us","password":"Malc2018","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false}, - {"email":"casio@73k.us","password":"Casio2011","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false} -]) + # if Mix.env() == :dev do + this_path = Path.dirname(__ENV__.file) + users_json = Path.join(this_path, "MOCK_DATA_users.json") -# for random week_start_at values -[head | tail] = Shift73k.weekdays() -week_starts = [head | Enum.drop(tail, 4)] + count_to_take = 15 -mock_users = - extra_mock_users - |> Jason.decode!() - |> Stream.concat(mock_users) - |> Enum.map(fn e -> - add_dt = NaiveDateTime.from_iso8601!(e["inserted_at"]) + mock_users = users_json |> File.read!() |> Jason.decode!() |> Enum.take_random(count_to_take) - %{ - email: e["email"], - role: String.to_existing_atom(e["role"]), - hashed_password: Bcrypt.hash_pwd_salt(e["password"]), - week_start_at: Enum.at(week_starts, Enum.random(0..2)), - calendar_slug: Ecto.UUID.generate(), - inserted_at: add_dt, - updated_at: add_dt, - confirmed_at: (e["confirmed_at"] && NaiveDateTime.add(add_dt, 300, :second)) || nil - } - end) + extra_mock_users = ~s([ + {"email":"adam@73k.us","password":"adamadamA1","role":"admin","inserted_at":"2018-12-14T01:01:01Z","confirmed_at":true}, + {"email":"kat@73k.us","password":"katkatA1","role":"manager","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":true}, + {"email":"babka@73k.us","password":"Babka2020","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false}, + {"email":"malcolm@73k.us","password":"Malc2018","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false}, + {"email":"casio@73k.us","password":"Casio2011","role":"user","inserted_at":"2018-12-14T01:06:01Z","confirmed_at":false} + ]) -Repo.insert_all(User, mock_users) -# end + # for random week_start_at values + [head | tail] = Shift73k.weekdays() + week_starts = [head | Enum.drop(tail, 4)] -##### -# shift tepmlates -alias Shift73k.Shifts.Templates.ShiftTemplate + mock_users = + extra_mock_users + |> Jason.decode!() + |> Stream.concat(mock_users) + |> Enum.map(fn e -> + add_dt = NaiveDateTime.from_iso8601!(e["inserted_at"]) -shifts_json = Path.join(this_path, "MOCK_DATA_shift-templates.json") -mock_shifts = shifts_json |> File.read!() |> Jason.decode!() + %{ + email: e["email"], + role: String.to_existing_atom(e["role"]), + hashed_password: Bcrypt.hash_pwd_salt(e["password"]), + week_start_at: Enum.at(week_starts, Enum.random(0..2)), + calendar_slug: Ecto.UUID.generate(), + inserted_at: add_dt, + updated_at: add_dt, + confirmed_at: (e["confirmed_at"] && NaiveDateTime.add(add_dt, 300, :second)) || nil + } + end) + + Repo.insert_all(User, mock_users) + # end + + ##### + # shift tepmlates + alias Shift73k.Shifts.Templates.ShiftTemplate + + shifts_json = Path.join(this_path, "MOCK_DATA_shift-templates.json") + mock_shifts = shifts_json |> File.read!() |> Jason.decode!() + + time_from_mock = fn mock_time -> + case String.length(mock_time) do + 4 -> Time.from_iso8601!("T0#{mock_time}:00") + 5 -> Time.from_iso8601!("T#{mock_time}:00") + end + end + + seconds_day = 86_400 + seconds_days_14 = seconds_day * 14 + seconds_half_day = Integer.floor_div(seconds_day, 2) + + for user <- Accounts.list_users() do + user_shifts = + mock_shifts + |> Enum.take_random(:rand.uniform(15) + 5) + |> Enum.map(fn e -> + seconds_to_add = :rand.uniform(seconds_days_14) + seconds_half_day + add_dt = NaiveDateTime.add(user.inserted_at, seconds_to_add) + time_start = time_from_mock.(e["time_start"]) + shift_len_min = e["length_minutes"] || 0 + shift_length = e["length_hours"] * 60 * 60 + shift_len_min * 60 + time_end = Time.add(time_start, shift_length) |> Time.truncate(:second) + + %{ + subject: e["subject"], + description: e["description"], + location: e["location"], + time_zone: Tzdata.zone_list() |> Enum.random(), + time_start: time_start, + time_end: time_end, + user_id: user.id, + inserted_at: add_dt, + updated_at: add_dt + } + end) + + Repo.insert_all(ShiftTemplate, user_shifts) + end + + ##### + # insert shifts for each user? + alias Shift73k.Shifts + alias Shift73k.Shifts.Templates + + for user <- Accounts.list_users() do + # build a date range for the time from 120 days ago to 120 days from now + today = Date.utc_today() + date_range = Date.range(Date.add(today, -120), Date.add(today, 120)) + + # get 3 random shift templates for user + st_list = Templates.list_shift_templates_by_user(user.id) |> Enum.take_random(3) + + for st <- st_list do + days_to_schedule = Enum.take_random(date_range, 47) + shift_data = ShiftTemplate.attrs(st) + + days_to_schedule + |> Stream.map(&Map.put(shift_data, :date, &1)) + |> Enum.map(&Repo.timestamp/1) + |> Shifts.create_multiple() + end + end -time_from_mock = fn mock_time -> - case String.length(mock_time) do - 4 -> Time.from_iso8601!("T0#{mock_time}:00") - 5 -> Time.from_iso8601!("T#{mock_time}:00") - end -end - -seconds_day = 86_400 -seconds_days_14 = seconds_day * 14 -seconds_half_day = Integer.floor_div(seconds_day, 2) - -for user <- Accounts.list_users() do - user_shifts = - mock_shifts - |> Enum.take_random(:rand.uniform(15) + 5) - |> Enum.map(fn e -> - seconds_to_add = :rand.uniform(seconds_days_14) + seconds_half_day - add_dt = NaiveDateTime.add(user.inserted_at, seconds_to_add) - time_start = time_from_mock.(e["time_start"]) - shift_len_min = e["length_minutes"] || 0 - shift_length = e["length_hours"] * 60 * 60 + shift_len_min * 60 - time_end = Time.add(time_start, shift_length) |> Time.truncate(:second) - - %{ - subject: e["subject"], - description: e["description"], - location: e["location"], - time_zone: Tzdata.zone_list() |> Enum.random(), - time_start: time_start, - time_end: time_end, - user_id: user.id, - inserted_at: add_dt, - updated_at: add_dt - } - end) - - Repo.insert_all(ShiftTemplate, user_shifts) -end - -##### -# insert shifts for each user? -alias Shift73k.Shifts -alias Shift73k.Shifts.Templates - -for user <- Accounts.list_users() do - # build a date range for the time from 120 days ago to 120 days from now - today = Date.utc_today() - date_range = Date.range(Date.add(today, -120), Date.add(today, 120)) - - # get 3 random shift templates for user - st_list = Templates.list_shift_templates_by_user(user.id) |> Enum.take_random(3) - - for st <- st_list do - days_to_schedule = Enum.take_random(date_range, 47) - shift_data = ShiftTemplate.attrs(st) - - days_to_schedule - |> Stream.map(&Map.put(shift_data, :date, &1)) - |> Enum.map(&Repo.timestamp/1) - |> Shifts.create_multiple() end + end