From 2839a7ee07f8e1dff904a41f3fddf65b0e3de3ac Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Mon, 26 Feb 2024 19:14:25 +0700 Subject: [PATCH 1/9] Can now create new space --- lib/polar/accounts.ex | 8 ++ lib/polar/accounts/space.ex | 4 +- lib/polar/accounts/space/manager.ex | 4 + .../components/layouts/app.html.heex | 18 ++- lib/polar_web/live/dashboard/data_loader.ex | 16 +++ .../live/dashboard/space/new_live.ex | 102 +++++++++++++++++ lib/polar_web/live/dashboard/space_live.ex | 41 +++++++ lib/polar_web/live/dashboard_live.ex | 106 ++++++++++++++++++ lib/polar_web/live/user_settings_live.ex | 2 +- lib/polar_web/router.ex | 5 + ...0226104718_remove_cdn_host_from_spaces.exs | 9 ++ 11 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 lib/polar_web/live/dashboard/data_loader.ex create mode 100644 lib/polar_web/live/dashboard/space/new_live.ex create mode 100644 lib/polar_web/live/dashboard/space_live.ex create mode 100644 lib/polar_web/live/dashboard_live.ex create mode 100644 priv/repo/migrations/20240226104718_remove_cdn_host_from_spaces.exs diff --git a/lib/polar/accounts.ex b/lib/polar/accounts.ex index f216f40..39d0ae5 100644 --- a/lib/polar/accounts.ex +++ b/lib/polar/accounts.ex @@ -12,6 +12,14 @@ defmodule Polar.Accounts do to: Space.Manager, as: :create + defdelegate change_space(space), + to: Space.Manager, + as: :change + + defdelegate change_space(space, attrs), + to: Space.Manager, + as: :change + defdelegate get_space_credential(query), to: Space.Manager, as: :get_credential diff --git a/lib/polar/accounts/space.ex b/lib/polar/accounts/space.ex index d3accb4..fabd232 100644 --- a/lib/polar/accounts/space.ex +++ b/lib/polar/accounts/space.ex @@ -6,7 +6,6 @@ defmodule Polar.Accounts.Space do schema "spaces" do field :name, :string - field :cdn_host, :string belongs_to :owner, User @@ -16,7 +15,8 @@ defmodule Polar.Accounts.Space do @doc false def changeset(space, attrs) do space - |> cast(attrs, [:name, :cdn_host]) + |> cast(attrs, [:name]) |> validate_required([:name]) + |> unique_constraint(:name, name: :spaces_owner_id_name_index) end end diff --git a/lib/polar/accounts/space/manager.ex b/lib/polar/accounts/space/manager.ex index a576c8a..8a3656e 100644 --- a/lib/polar/accounts/space/manager.ex +++ b/lib/polar/accounts/space/manager.ex @@ -11,6 +11,10 @@ defmodule Polar.Accounts.Space.Manager do |> Repo.insert() end + def change(%Space{} = space, attrs \\ %{}) do + Space.changeset(space, attrs) + end + def get_credential(token: token) do Space.Credential.scope(:active, Space.Credential) |> Repo.get_by(token: token) diff --git a/lib/polar_web/components/layouts/app.html.heex b/lib/polar_web/components/layouts/app.html.heex index d3dde72..5ed7301 100644 --- a/lib/polar_web/components/layouts/app.html.heex +++ b/lib/polar_web/components/layouts/app.html.heex @@ -21,6 +21,18 @@ > <%= gettext("Home") %> + <.link + navigate={~p"/dashboard"} + class={ + if assigns[:current_path] == ~p"/dashboard", + do: "bg-slate-900 text-white rounded-md px-3 py-2 text-sm font-medium", + else: + "rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-900 hover:text-white" + } + aria-current="page" + > + <%= gettext("Dashboard") %> + @@ -28,7 +40,7 @@
<%= if @current_user do %> <.link - href={~p"/users/settings"} + navigate={~p"/users/settings"} class={ if assigns[:current_path] == ~p"/users/settings", do: "bg-slate-900 text-white rounded-md px-3 py-2 text-sm font-medium", @@ -47,13 +59,13 @@ <% else %> <.link - href={~p"/users/log_in"} + navigate={~p"/users/log_in"} class="text-slate-300 rounded-md px-3 py-2 text-sm font-medium hover:bg-slate-900 hover:text-white" > <%= gettext("Sign In") %> <.link - href={~p"/users/register"} + navigate={~p"/users/register"} class="text-slate-300 rounded-md px-3 py-2 text-sm font-medium hover:bg-slate-900 hover:text-white" > <%= gettext("Register") %> diff --git a/lib/polar_web/live/dashboard/data_loader.ex b/lib/polar_web/live/dashboard/data_loader.ex new file mode 100644 index 0000000..399398b --- /dev/null +++ b/lib/polar_web/live/dashboard/data_loader.ex @@ -0,0 +1,16 @@ +defmodule PolarWeb.Dashboard.DataLoader do + alias Polar.Repo + alias Polar.Accounts.Space + + import Ecto.Query, only: [from: 2] + + def load_spaces(user) do + from(s in Space, + where: s.owner_id == ^user.id, + limit: 5, + order_by: [desc: :updated_at] + ) + |> Repo.all() + |> Repo.preload([:owner]) + end +end diff --git a/lib/polar_web/live/dashboard/space/new_live.ex b/lib/polar_web/live/dashboard/space/new_live.ex new file mode 100644 index 0000000..7d7ec25 --- /dev/null +++ b/lib/polar_web/live/dashboard/space/new_live.ex @@ -0,0 +1,102 @@ +defmodule PolarWeb.Dashboard.Space.NewLive do + use PolarWeb, :live_view + + alias Polar.Accounts + alias Polar.Accounts.Space + + def render(assigns) do + ~H""" +
+
+
+

+ <%= gettext("Create a space") %> +

+

+ <%= gettext( + "Spaces encapsulate all your tokens, it can represent an organization or just a workspace." + ) %> +

+
+ + <.simple_form + for={@space_form} + id="new-space-form" + phx-submit="save" + phx-change="validate" + class="bg-white shadow-sm ring-1 ring-slate-900/5 sm:rounded-xl md:col-span-2" + > +
+
+
+ <.input field={@space_form[:name]} label={gettext("Name")} required /> +
+
+
+ <:actions> +
+ <.link navigate={~p"/dashboard"} class="text-sm font-semibold leading-6 text-gray-900"> + Cancel + + <.button + type="submit" + class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <%= gettext("Save") %> + +
+ + +
+
+ """ + end + + def mount(_params, _session, socket) do + space_form = to_form(Accounts.change_space(%Space{})) + + socket = + socket + |> assign(:space_form, space_form) + |> assign(:page_title, gettext("New space")) + |> assign(:current_path, ~p"/dashboard") + + {:ok, socket} + end + + def handle_event("validate", %{"space" => space_params} = params, %{assigns: assigns} = socket) do + space_form = + %Space{owner_id: assigns.current_user.id} + |> Accounts.change_space(space_params) + |> Map.put(:action, :validate) + |> to_form() + + socket = + socket + |> assign(:space_form, space_form) + + {:noreply, socket} + end + + def handle_event("save", %{"space" => space_params}, %{assigns: assigns} = socket) do + case Accounts.create_space(assigns.current_user, space_params) do + {:ok, space} -> + socket = + socket + |> put_flash(:info, gettext("Space successfully created!")) + |> push_navigate(to: ~p"/dashboard") + + {:noreply, socket} + + {:error, %Ecto.Changeset{} = changeset} -> + space_form = to_form(changeset) + + socket = + socket + |> assign(:check_errors, true) + |> assign(:space_form, space_form) + + {:noreply, socket} + end + end +end diff --git a/lib/polar_web/live/dashboard/space_live.ex b/lib/polar_web/live/dashboard/space_live.ex new file mode 100644 index 0000000..d167700 --- /dev/null +++ b/lib/polar_web/live/dashboard/space_live.ex @@ -0,0 +1,41 @@ +defmodule PolarWeb.Dashboard.SpaceLive do + use PolarWeb, :live_view + + alias Polar.Repo + alias Polar.Accounts.Space + + def render(assigns) do + ~H""" +
+
+
+
+

+ <%= gettext("Credentials") %> +

+
+
+
+
+ """ + end + + def mount(%{"id" => id}, _session, %{assigns: assigns} = socket) do + space = Repo.get_by(Space, owner_id: assigns.current_user.id, id: id) + + if space do + socket = + socket + |> assign(:page_title, space.name) + + {:ok, socket} + else + socket = + socket + |> put_flash(:error, gettext("Space not found")) + |> push_navigate(to: ~p"/dashboard") + + {:ok, socket} + end + end +end diff --git a/lib/polar_web/live/dashboard_live.ex b/lib/polar_web/live/dashboard_live.ex new file mode 100644 index 0000000..bc7ed85 --- /dev/null +++ b/lib/polar_web/live/dashboard_live.ex @@ -0,0 +1,106 @@ +defmodule PolarWeb.DashboardLive do + use PolarWeb, :live_view + + import PolarWeb.Dashboard.DataLoader + + def render(assigns) do + ~H""" +
+
+
+
+

+ <%= gettext("Your spaces") %> +

+
+
0} class="ml-4 mt-2 flex-shrink-0"> + <.link + navigate={~p"/dashboard/spaces/new"} + class="relative inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <.icon name="hero-plus-solid" class="-ml-0.5 mr-1.5 h-5 w-5 text-white" /> + <%= gettext("Create new space") %> + +
+
+
+
+
+ +

<%= gettext("No spaces") %>

+

+ <%= gettext("Get started by creating a new space.") %> +

+
+ <.link + navigate={~p"/dashboard/spaces/new"} + class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <.icon name="hero-plus-solid" class="-ml-0.5 mr-1.5 h-5 w-5 text-white" /> + <%= gettext("Create new space") %> + +
+
+
+
    +
  • +
    +
    +

    <%= space.name %>

    +
    +
    +

    + <%= gettext("Created on") %> + +

    + + + +

    <%= gettext("Owned by") %> <%= space.owner.email %>

    +
    +
    +
    + <.link + navigate={~p"/dashboard/spaces/#{space.id}"} + class="hidden rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:block" + > + <%= gettext("View space") %> + +
    +
  • +
+
+ """ + end + + def mount(_params, _session, %{assigns: assigns} = socket) do + spaces = load_spaces(assigns.current_user) + + socket = + socket + |> assign(:current_path, ~p"/dashboard") + |> assign(:page_title, gettext("Dashboard")) + |> assign(:spaces, spaces) + + {:ok, socket} + end +end diff --git a/lib/polar_web/live/user_settings_live.ex b/lib/polar_web/live/user_settings_live.ex index e9ddc49..ff2e23a 100644 --- a/lib/polar_web/live/user_settings_live.ex +++ b/lib/polar_web/live/user_settings_live.ex @@ -5,7 +5,7 @@ defmodule PolarWeb.UserSettingsLive do def render(assigns) do ~H""" -
+
<.header class="px-4 sm:px-0">

diff --git a/lib/polar_web/router.ex b/lib/polar_web/router.ex index c211313..6c593db 100644 --- a/lib/polar_web/router.ex +++ b/lib/polar_web/router.ex @@ -49,6 +49,11 @@ defmodule PolarWeb.Router do live_session :require_authenticated_user, on_mount: [{PolarWeb.UserAuth, :ensure_authenticated}] do + live "/dashboard", DashboardLive, :show + + live "/dashboard/spaces/new", Dashboard.Space.NewLive, :new + live "/dashboard/spaces/:id", Dashboard.SpaceLive, :show + live "/users/settings", UserSettingsLive, :edit live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email end diff --git a/priv/repo/migrations/20240226104718_remove_cdn_host_from_spaces.exs b/priv/repo/migrations/20240226104718_remove_cdn_host_from_spaces.exs new file mode 100644 index 0000000..bbeed16 --- /dev/null +++ b/priv/repo/migrations/20240226104718_remove_cdn_host_from_spaces.exs @@ -0,0 +1,9 @@ +defmodule Polar.Repo.Migrations.RemoveCDNHostFromSpaces do + use Ecto.Migration + + def change do + alter table(:spaces) do + remove :cdn_host + end + end +end From 60886a12dfbc651e77f615b02657f9737263223a Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Mon, 26 Feb 2024 19:24:00 +0700 Subject: [PATCH 2/9] Update timestamp on access --- lib/polar/streams/item/manager.ex | 4 +++- lib/polar_web/controllers/streams/item_controller.ex | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/polar/streams/item/manager.ex b/lib/polar/streams/item/manager.ex index 79c8c37..75af3dd 100644 --- a/lib/polar/streams/item/manager.ex +++ b/lib/polar/streams/item/manager.ex @@ -19,8 +19,10 @@ defmodule Polar.Streams.Item.Manager do |> Repo.insert!() %Item.Access{id: item_access_id} = item_access -> + timestamp = DateTime.utc_now() + from(ia in Item.Access, - update: [inc: [count: 1]], + update: [inc: [count: 1], set: [updated_at: ^timestamp]], where: ia.id == ^item_access_id ) |> Repo.update_all([]) diff --git a/lib/polar_web/controllers/streams/item_controller.ex b/lib/polar_web/controllers/streams/item_controller.ex index e97c821..4d310d0 100644 --- a/lib/polar_web/controllers/streams/item_controller.ex +++ b/lib/polar_web/controllers/streams/item_controller.ex @@ -22,9 +22,7 @@ defmodule PolarWeb.Streams.ItemController do Streams.record_item_access(item, credential) - endpoint = credential.space.cdn_host || default_cdn_host - - url = Path.join(["https://", endpoint, item.path]) + url = Path.join(["https://", default_cdn_host, item.path]) redirect(conn, external: url) end From e981c1ce71a874c96e896702834320c171abd7f7 Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Mon, 26 Feb 2024 19:50:34 +0700 Subject: [PATCH 3/9] Clean up 404 page --- config/dev.exs | 2 +- lib/polar_web/controllers/error_html.ex | 8 +- .../controllers/error_html/404.html.heex | 81 +++++++++++++++++++ .../live/dashboard/space/new_live.ex | 2 +- lib/polar_web/live/dashboard/space_live.ex | 19 ++--- 5 files changed, 92 insertions(+), 20 deletions(-) create mode 100644 lib/polar_web/controllers/error_html/404.html.heex diff --git a/config/dev.exs b/config/dev.exs index 15ad692..5fd89d0 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -22,7 +22,7 @@ config :polar, PolarWeb.Endpoint, http: [ip: {0, 0, 0, 0}, port: 4000], check_origin: false, code_reloader: true, - debug_errors: true, + debug_errors: false, secret_key_base: "TzqTbHuNO4m/845kDoNhFVdt2NYb0Ql8IQudyE594mks0WPM6jgK4DiSDrAZQTsJ", watchers: [ esbuild: {Esbuild, :install_and_run, [:polar, ~w(--sourcemap=inline --watch)]}, diff --git a/lib/polar_web/controllers/error_html.ex b/lib/polar_web/controllers/error_html.ex index c96e7dc..8391e39 100644 --- a/lib/polar_web/controllers/error_html.ex +++ b/lib/polar_web/controllers/error_html.ex @@ -8,12 +8,12 @@ defmodule PolarWeb.ErrorHTML do # * lib/polar_web/controllers/error_html/404.html.heex # * lib/polar_web/controllers/error_html/500.html.heex # - # embed_templates "error_html/*" + embed_templates "error_html/*" # The default is to render a plain text page based on # the template name. For example, "404.html" becomes # "Not Found". - def render(template, _assigns) do - Phoenix.Controller.status_message_from_template(template) - end + # def render(template, _assigns) do + # Phoenix.Controller.status_message_from_template(template) + # end end diff --git a/lib/polar_web/controllers/error_html/404.html.heex b/lib/polar_web/controllers/error_html/404.html.heex new file mode 100644 index 0000000..c926c09 --- /dev/null +++ b/lib/polar_web/controllers/error_html/404.html.heex @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + <.live_title suffix=" · Polar"> + <%= "Page Not Found" %> + + + + <%= if domain = System.get_env("PHX_HOST") do %> + + <% end %> + + +
+

<%= gettext("404") %>

+

+ <%= gettext("This page does not exist") %> +

+

+ <% gettext("Sorry, we couldn’t find the page you’re looking for.") %> +

+
+
+

<%= gettext("Check out other pages") %>

+
    +
  • +
    + <.icon name="hero-home-solid" class="h-6 w-6 text-indigo-400" /> +
    +
    +

    + + <%= gettext( + "Take me home" + ) %> + +

    +

    + <%= gettext("to the place I belong. West Virginia.") %> +

    +
    +
    + <.icon name="hero-chevron-right-solid" class="h-5 w-5 text-slate-400" /> +
    +
  • +
  • +
    + <.icon name="hero-squares-plus-solid" class="h-6 w-6 text-indigo-400" /> +
    +
    +

    + + <%= gettext("Dashboard") %> + +

    +

    + <%= gettext("Go back to the dashboard.") %> +

    +
    +
    + <.icon name="hero-chevron-right-solid" class="h-5 w-5 text-slate-400" /> +
    +
  • +
+
+ + diff --git a/lib/polar_web/live/dashboard/space/new_live.ex b/lib/polar_web/live/dashboard/space/new_live.ex index 7d7ec25..3bcf171 100644 --- a/lib/polar_web/live/dashboard/space/new_live.ex +++ b/lib/polar_web/live/dashboard/space/new_live.ex @@ -14,7 +14,7 @@ defmodule PolarWeb.Dashboard.Space.NewLive do

<%= gettext( - "Spaces encapsulate all your tokens, it can represent an organization or just a workspace." + "Spaces group all your tokens, it can represent an organization or just a workspace." ) %>

diff --git a/lib/polar_web/live/dashboard/space_live.ex b/lib/polar_web/live/dashboard/space_live.ex index d167700..63b09be 100644 --- a/lib/polar_web/live/dashboard/space_live.ex +++ b/lib/polar_web/live/dashboard/space_live.ex @@ -21,21 +21,12 @@ defmodule PolarWeb.Dashboard.SpaceLive do end def mount(%{"id" => id}, _session, %{assigns: assigns} = socket) do - space = Repo.get_by(Space, owner_id: assigns.current_user.id, id: id) + space = Repo.get_by!(Space, owner_id: assigns.current_user.id, id: id) - if space do - socket = - socket - |> assign(:page_title, space.name) + socket = + socket + |> assign(:page_title, space.name) - {:ok, socket} - else - socket = - socket - |> put_flash(:error, gettext("Space not found")) - |> push_navigate(to: ~p"/dashboard") - - {:ok, socket} - end + {:ok, socket} end end From 39c06912913009af31b806b6693c2204922fac13 Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Mon, 26 Feb 2024 19:54:11 +0700 Subject: [PATCH 4/9] Update error page test --- test/polar_web/controllers/error_html_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/polar_web/controllers/error_html_test.exs b/test/polar_web/controllers/error_html_test.exs index 82ff4d4..fc9cf51 100644 --- a/test/polar_web/controllers/error_html_test.exs +++ b/test/polar_web/controllers/error_html_test.exs @@ -5,7 +5,7 @@ defmodule PolarWeb.ErrorHTMLTest do import Phoenix.Template test "renders 404.html" do - assert render_to_string(PolarWeb.ErrorHTML, "404", "html", []) == "Not Found" + assert render_to_string(PolarWeb.ErrorHTML, "404", "html", []) =~ "Not Found" end test "renders 500.html" do From bd52aeaa16495fe375dda30f3c6c9342e0325985 Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Tue, 27 Feb 2024 14:47:27 +0700 Subject: [PATCH 5/9] MVP for issuing credentials --- config/dev.exs | 2 +- lib/polar/accounts.ex | 8 + lib/polar/accounts/space/credential.ex | 5 +- lib/polar/accounts/space/manager.ex | 4 + lib/polar_web/components/core_components.ex | 11 ++ .../live/dashboard/credential/new_live.ex | 183 ++++++++++++++++++ .../live/dashboard/credential_live.ex | 78 ++++++++ .../live/dashboard/space/data_loader.ex | 15 ++ .../live/dashboard/space/new_live.ex | 4 +- lib/polar_web/live/dashboard/space_live.ex | 75 +++++++ lib/polar_web/live/dashboard_live.ex | 18 +- lib/polar_web/router.ex | 3 + ...ange_unique_index_on_space_credentials.exs | 8 + 13 files changed, 394 insertions(+), 20 deletions(-) create mode 100644 lib/polar_web/live/dashboard/credential/new_live.ex create mode 100644 lib/polar_web/live/dashboard/credential_live.ex create mode 100644 lib/polar_web/live/dashboard/space/data_loader.ex create mode 100644 priv/repo/migrations/20240227054418_change_unique_index_on_space_credentials.exs diff --git a/config/dev.exs b/config/dev.exs index 5fd89d0..15ad692 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -22,7 +22,7 @@ config :polar, PolarWeb.Endpoint, http: [ip: {0, 0, 0, 0}, port: 4000], check_origin: false, code_reloader: true, - debug_errors: false, + debug_errors: true, secret_key_base: "TzqTbHuNO4m/845kDoNhFVdt2NYb0Ql8IQudyE594mks0WPM6jgK4DiSDrAZQTsJ", watchers: [ esbuild: {Esbuild, :install_and_run, [:polar, ~w(--sourcemap=inline --watch)]}, diff --git a/lib/polar/accounts.ex b/lib/polar/accounts.ex index 39d0ae5..e242e63 100644 --- a/lib/polar/accounts.ex +++ b/lib/polar/accounts.ex @@ -24,6 +24,14 @@ defmodule Polar.Accounts do to: Space.Manager, as: :get_credential + defdelegate change_space_credential(credential), + to: Space.Manager, + as: :change_credential + + defdelegate change_space_credential(credential, attrs), + to: Space.Manager, + as: :change_credential + defdelegate create_space_credential(space, user, params), to: Space.Manager, as: :create_credential diff --git a/lib/polar/accounts/space/credential.ex b/lib/polar/accounts/space/credential.ex index 52a0753..421022c 100644 --- a/lib/polar/accounts/space/credential.ex +++ b/lib/polar/accounts/space/credential.ex @@ -39,6 +39,8 @@ defmodule Polar.Accounts.Space.Credential do def expires_in_range, do: @expires_in_range + def types, do: ["lxd", "incus"] + @doc false def changeset(credential, attrs) do expires_in_range_values = Enum.map(@expires_in_range, fn r -> r.value end) @@ -47,9 +49,10 @@ defmodule Polar.Accounts.Space.Credential do |> cast(attrs, [:name, :expires_in, :type]) |> generate_token() |> validate_inclusion(:expires_in, expires_in_range_values) - |> validate_inclusion(:type, ["lxd", "incus"]) + |> validate_inclusion(:type, types()) |> maybe_set_expires_at() |> validate_required([:token, :type, :name]) + |> unique_constraint(:name, name: :space_credentials_space_id_name_index) end def scope(:active, queryable) do diff --git a/lib/polar/accounts/space/manager.ex b/lib/polar/accounts/space/manager.ex index 8a3656e..2022abf 100644 --- a/lib/polar/accounts/space/manager.ex +++ b/lib/polar/accounts/space/manager.ex @@ -20,6 +20,10 @@ defmodule Polar.Accounts.Space.Manager do |> Repo.get_by(token: token) end + def change_credential(credential_or_changeset, attrs \\ %{}) do + Space.Credential.changeset(credential_or_changeset, attrs) + end + def create_credential(%Accounts.Space{} = space, user, params) do %Space.Credential{space_id: space.id} |> Space.Credential.changeset(params) diff --git a/lib/polar_web/components/core_components.ex b/lib/polar_web/components/core_components.ex index 9adc7ce..53309f0 100644 --- a/lib/polar_web/components/core_components.ex +++ b/lib/polar_web/components/core_components.ex @@ -389,6 +389,17 @@ defmodule PolarWeb.CoreComponents do """ end + def input(%{type: "radio"} = assigns) do + assigns = + assign_new(assigns, :checked, fn -> + Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value]) + end) + + ~H""" + + """ + end + # All other inputs text, datetime-local, url, password, etc. are handled here... def input(assigns) do ~H""" diff --git a/lib/polar_web/live/dashboard/credential/new_live.ex b/lib/polar_web/live/dashboard/credential/new_live.ex new file mode 100644 index 0000000..daf8d9b --- /dev/null +++ b/lib/polar_web/live/dashboard/credential/new_live.ex @@ -0,0 +1,183 @@ +defmodule PolarWeb.Dashboard.Credential.NewLive do + alias PolarWeb.CoreComponents + use PolarWeb, :live_view + + alias Polar.Repo + alias Polar.Accounts + alias Polar.Accounts.Space + + def render(assigns) do + ~H""" +
+
+
+

+ <%= gettext("Create a credential") %> +

+

+ <%= gettext("Credentials allow you to control your simplestreams feed.") %> +

+
+ <.simple_form + for={@credential_form} + id="new-credential-form" + phx-submit="save" + phx-change="validate" + class="bg-white shadow-sm ring-1 ring-slate-900/5 sm:rounded-xl md:col-span-2" + > +
+
+
+ <.input field={@credential_form[:name]} label={gettext("Name")} required /> +
+
+
+

+ <%= gettext("Expiry days") %> +

+
+
+ <%= gettext("Choose expiry policy") %> + +
+ +
+
+
+
+
+

+ <%= gettext("Type") %> +

+
+
+ <%= gettext("Choose credential type") %> + +
+ +
+
+ <.error :for={ + msg <- Enum.map(@credential_form[:type].errors, &CoreComponents.translate_error/1) + }> + <%= msg %> + +
+
+
+ <:actions> +
+ <.link + navigate={~p"/dashboard/spaces/#{@space.id}"} + class="text-sm font-semibold leading-6 text-gray-900" + > + <%= gettext("Cancel") %> + + <.button + type="submit" + class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <%= gettext("Save") %> + +
+ + +
+
+ """ + end + + def mount(%{"space_id" => space_id}, _session, %{assigns: assigns} = socket) do + space = Repo.get_by!(Space, owner_id: assigns.current_user.id, id: space_id) + + credential_form = + to_form(Accounts.change_space_credential(%Space.Credential{space_id: space.id})) + + socket = + socket + |> assign(:credential_form, credential_form) + |> assign(:space, space) + |> assign(:page_title, gettext("New credential")) + |> assign(:current_path, ~p"/dashboard") + + {:ok, socket} + end + + def handle_event("validate", %{"credential" => credential_params}, %{assigns: assigns} = socket) do + credential_form = + assigns.credential_form.source + |> Accounts.change_space_credential(credential_params) + |> Map.put(:action, :validate) + |> to_form() + + socket = + socket + |> assign(:credential_form, credential_form) + + {:noreply, socket} + end + + def handle_event("save", %{"credential" => credential_params}, %{assigns: assigns} = socket) do + case Accounts.create_space_credential(assigns.space, assigns.current_user, credential_params) do + {:ok, credential} -> + socket = + socket + |> put_flash(:info, gettext("Credential successfully created!")) + |> push_navigate( + to: ~p"/dashboard/spaces/#{assigns.space.id}/credentials/#{credential.id}" + ) + + {:noreply, socket} + + {:error, %Ecto.Changeset{} = changeset} -> + credential_form = to_form(changeset) + + socket = + socket + |> assign(:check_errors, true) + |> assign(:credential_form, credential_form) + + {:noreply, socket} + end + end +end diff --git a/lib/polar_web/live/dashboard/credential_live.ex b/lib/polar_web/live/dashboard/credential_live.ex new file mode 100644 index 0000000..9fb9ba5 --- /dev/null +++ b/lib/polar_web/live/dashboard/credential_live.ex @@ -0,0 +1,78 @@ +defmodule PolarWeb.Dashboard.CredentialLive do + use PolarWeb, :live_view + + alias Polar.Repo + alias Polar.Accounts.Space + + @cli_tools %{ + "lxd" => "lxc", + "incus" => "incus" + } + + def render(assigns) do + ~H""" +
+
+

+ <%= gettext("Credential Information") %> +

+

+ <%= gettext("Details about the credential and instructions on how to use it.") %> +

+
+
+
+
+
<%= gettext("Name") %>
+
+ <%= @credential.name %> +
+
+
+
<%= gettext("Type") %>
+
+ <%= @credential.type %> +
+
+
+
<%= gettext("Expires At") %>
+
+ <%= if @credential.expires_at do %> + <%= Calendar.strftime( + @credential.expires_at, + "%d %b %Y" + ) %> + <% else %> + <%= gettext("Never Expires") %> + <% end %> +
+
+
+
+ <%= Map.fetch!(@cli_tools, @credential.type) %> remote add opsmaru <%= url( + @socket, + ~p"/spaces/#{@credential.token}" + ) %> +
+
+
+
+
+ """ + end + + def mount(%{"space_id" => space_id, "id" => id}, _session, %{assigns: assigns} = socket) do + space = Repo.get_by!(Space, owner_id: assigns.current_user.id, id: space_id) + credential = Repo.get_by!(Space.Credential, space_id: space.id, id: id) + + socket = + socket + |> assign(:page_title, credential.name) + |> assign(:current_path, ~p"/dashboard") + |> assign(:space, space) + |> assign(:credential, credential) + |> assign(:cli_tools, @cli_tools) + + {:ok, socket} + end +end diff --git a/lib/polar_web/live/dashboard/space/data_loader.ex b/lib/polar_web/live/dashboard/space/data_loader.ex new file mode 100644 index 0000000..d3bbbda --- /dev/null +++ b/lib/polar_web/live/dashboard/space/data_loader.ex @@ -0,0 +1,15 @@ +defmodule PolarWeb.Dashboard.Space.DataLoader do + alias Polar.Repo + alias Polar.Accounts.Space + + import Ecto.Query, only: [from: 2] + + def load_credentials(%Space{} = space) do + from(sc in Space.Credential, + where: + sc.space_id == ^space.id and + sc.current_state == ^"active" + ) + |> Repo.all() + end +end diff --git a/lib/polar_web/live/dashboard/space/new_live.ex b/lib/polar_web/live/dashboard/space/new_live.ex index 3bcf171..84bc22c 100644 --- a/lib/polar_web/live/dashboard/space/new_live.ex +++ b/lib/polar_web/live/dashboard/space/new_live.ex @@ -64,7 +64,7 @@ defmodule PolarWeb.Dashboard.Space.NewLive do {:ok, socket} end - def handle_event("validate", %{"space" => space_params} = params, %{assigns: assigns} = socket) do + def handle_event("validate", %{"space" => space_params}, %{assigns: assigns} = socket) do space_form = %Space{owner_id: assigns.current_user.id} |> Accounts.change_space(space_params) @@ -84,7 +84,7 @@ defmodule PolarWeb.Dashboard.Space.NewLive do socket = socket |> put_flash(:info, gettext("Space successfully created!")) - |> push_navigate(to: ~p"/dashboard") + |> push_navigate(to: ~p"/dashboard/spaces/#{space.id}") {:noreply, socket} diff --git a/lib/polar_web/live/dashboard/space_live.ex b/lib/polar_web/live/dashboard/space_live.ex index 63b09be..8dc31f8 100644 --- a/lib/polar_web/live/dashboard/space_live.ex +++ b/lib/polar_web/live/dashboard/space_live.ex @@ -4,6 +4,8 @@ defmodule PolarWeb.Dashboard.SpaceLive do alias Polar.Repo alias Polar.Accounts.Space + import PolarWeb.Dashboard.Space.DataLoader + def render(assigns) do ~H"""
@@ -14,8 +16,76 @@ defmodule PolarWeb.Dashboard.SpaceLive do <%= gettext("Credentials") %>
+
0} class="ml-4 mt-2 flex-shrink-0"> + <.link + navigate={~p"/dashboard/spaces/#{@space.id}/credentials/new"} + class="relative inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <.icon name="hero-plus-solid" class="-ml-0.5 mr-1.5 h-5 w-5 text-white" /> + <%= gettext("Create new credential") %> + +
+
+
+
+
+ <.icon name="hero-key" class="mx-auto h-12 w-12 text-slate-400" /> +

<%= gettext("No credential") %>

+

+ <%= gettext("Get started by creating a new credential.") %> +

+
+ <.link + navigate={~p"/dashboard/spaces/#{@space.id}/credentials/new"} + class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + > + <.icon name="hero-plus-solid" class="-ml-0.5 mr-1.5 h-5 w-5 text-white" /> + <%= gettext("Create new credential") %> + +
+
""" end @@ -23,9 +93,14 @@ defmodule PolarWeb.Dashboard.SpaceLive do def mount(%{"id" => id}, _session, %{assigns: assigns} = socket) do space = Repo.get_by!(Space, owner_id: assigns.current_user.id, id: id) + credentials = load_credentials(space) + socket = socket |> assign(:page_title, space.name) + |> assign(:current_path, ~p"/dashboard") + |> assign(:space, space) + |> assign(:credentials, credentials) {:ok, socket} end diff --git a/lib/polar_web/live/dashboard_live.ex b/lib/polar_web/live/dashboard_live.ex index bc7ed85..ca792b3 100644 --- a/lib/polar_web/live/dashboard_live.ex +++ b/lib/polar_web/live/dashboard_live.ex @@ -26,21 +26,7 @@ defmodule PolarWeb.DashboardLive do
- + <.icon name="hero-folder-plus" class="mx-auto h-12 w-12 text-slate-400" />

<%= gettext("No spaces") %>

<%= gettext("Get started by creating a new space.") %> @@ -69,7 +55,7 @@ defmodule PolarWeb.DashboardLive do

<%= gettext("Created on") %>

diff --git a/lib/polar_web/router.ex b/lib/polar_web/router.ex index 6c593db..4a2ede8 100644 --- a/lib/polar_web/router.ex +++ b/lib/polar_web/router.ex @@ -54,6 +54,9 @@ defmodule PolarWeb.Router do live "/dashboard/spaces/new", Dashboard.Space.NewLive, :new live "/dashboard/spaces/:id", Dashboard.SpaceLive, :show + live "/dashboard/spaces/:space_id/credentials/new", Dashboard.Credential.NewLive, :new + live "/dashboard/spaces/:space_id/credentials/:id", Dashboard.CredentialLive, :show + live "/users/settings", UserSettingsLive, :edit live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email end diff --git a/priv/repo/migrations/20240227054418_change_unique_index_on_space_credentials.exs b/priv/repo/migrations/20240227054418_change_unique_index_on_space_credentials.exs new file mode 100644 index 0000000..25c9538 --- /dev/null +++ b/priv/repo/migrations/20240227054418_change_unique_index_on_space_credentials.exs @@ -0,0 +1,8 @@ +defmodule Polar.Repo.Migrations.ChangeUniqueIndexOnSpaceCredentials do + use Ecto.Migration + + def change do + drop index(:space_credentials, [:name], unique: true) + create index(:space_credentials, [:space_id, :name], unique: true) + end +end From 1ed8735af8ba2a1c9e9341f9ef7eedb27b911c3b Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Tue, 27 Feb 2024 14:50:42 +0700 Subject: [PATCH 6/9] Clean up warning for token --- .../live/dashboard/credential_live.ex | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/polar_web/live/dashboard/credential_live.ex b/lib/polar_web/live/dashboard/credential_live.ex index 9fb9ba5..86233a8 100644 --- a/lib/polar_web/live/dashboard/credential_live.ex +++ b/lib/polar_web/live/dashboard/credential_live.ex @@ -48,7 +48,26 @@ defmodule PolarWeb.Dashboard.CredentialLive do
-
+
+
+
+ <.icon name="hero-exclamation-triangle-solid" class="h-5 w-5 text-yellow-400" /> +
+
+

+ <%= gettext("Do not share your credential") %> +

+
+

+ <%= gettext( + "This credential is only meant for you, please do not share it with external parties." + ) %> +

+
+
+
+
+
<%= Map.fetch!(@cli_tools, @credential.type) %> remote add opsmaru <%= url( @socket, ~p"/spaces/#{@credential.token}" From 4f918e292b0560b207f89d4500dffa8c16eab930 Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Tue, 27 Feb 2024 19:02:12 +0700 Subject: [PATCH 7/9] Create tests for MVP dashboard --- .../controllers/error_html/500.html.heex | 81 +++++++++++++++++++ lib/polar_web/controllers/space_controller.ex | 9 +++ lib/polar_web/live/dashboard/space_live.ex | 1 + lib/polar_web/live/dashboard_live.ex | 1 + lib/polar_web/router.ex | 2 + .../polar_web/controllers/error_html_test.exs | 2 +- .../dashboard/credential/new_live_test.exs | 0 .../live/dashboard/space/new_live_test.exs | 33 ++++++++ .../live/dashboard/space_live_test.exs | 65 +++++++++++++++ test/polar_web/live/dashboard_live_test.exs | 81 +++++++++++++++++++ 10 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 lib/polar_web/controllers/error_html/500.html.heex create mode 100644 lib/polar_web/controllers/space_controller.ex create mode 100644 test/polar_web/live/dashboard/credential/new_live_test.exs create mode 100644 test/polar_web/live/dashboard/space/new_live_test.exs create mode 100644 test/polar_web/live/dashboard/space_live_test.exs create mode 100644 test/polar_web/live/dashboard_live_test.exs diff --git a/lib/polar_web/controllers/error_html/500.html.heex b/lib/polar_web/controllers/error_html/500.html.heex new file mode 100644 index 0000000..13f2fbf --- /dev/null +++ b/lib/polar_web/controllers/error_html/500.html.heex @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + <.live_title suffix=" · Polar"> + <%= "Page Not Found" %> + + + + <%= if domain = System.get_env("PHX_HOST") do %> + + <% end %> + + +
+

<%= gettext("404") %>

+

+ <%= gettext("Whoops! Something went wrong.") %> +

+

+ <% gettext("Sorry, an error happened, we've logged the error.") %> +

+
+
+

<%= gettext("Check out other pages") %>

+
    +
  • +
    + <.icon name="hero-home-solid" class="h-6 w-6 text-indigo-400" /> +
    +
    +

    + + <%= gettext( + "Take me home" + ) %> + +

    +

    + <%= gettext("to the place I belong. West Virginia.") %> +

    +
    +
    + <.icon name="hero-chevron-right-solid" class="h-5 w-5 text-slate-400" /> +
    +
  • +
  • +
    + <.icon name="hero-squares-plus-solid" class="h-6 w-6 text-indigo-400" /> +
    +
    +

    + + <%= gettext("Dashboard") %> + +

    +

    + <%= gettext("Go back to the dashboard.") %> +

    +
    +
    + <.icon name="hero-chevron-right-solid" class="h-5 w-5 text-slate-400" /> +
    +
  • +
+
+ + diff --git a/lib/polar_web/controllers/space_controller.ex b/lib/polar_web/controllers/space_controller.ex new file mode 100644 index 0000000..fa51e35 --- /dev/null +++ b/lib/polar_web/controllers/space_controller.ex @@ -0,0 +1,9 @@ +defmodule PolarWeb.SpaceController do + use PolarWeb, :controller + + action_fallback PolarWeb.FallbackController + + def show(conn, %{"space_token" => _space_token}) do + redirect(conn, to: ~p"/") + end +end diff --git a/lib/polar_web/live/dashboard/space_live.ex b/lib/polar_web/live/dashboard/space_live.ex index 8dc31f8..c21c428 100644 --- a/lib/polar_web/live/dashboard/space_live.ex +++ b/lib/polar_web/live/dashboard/space_live.ex @@ -48,6 +48,7 @@ defmodule PolarWeb.Dashboard.SpaceLive do
  • diff --git a/lib/polar_web/live/dashboard_live.ex b/lib/polar_web/live/dashboard_live.ex index ca792b3..03a0e63 100644 --- a/lib/polar_web/live/dashboard_live.ex +++ b/lib/polar_web/live/dashboard_live.ex @@ -45,6 +45,7 @@ defmodule PolarWeb.DashboardLive do
    • diff --git a/lib/polar_web/router.ex b/lib/polar_web/router.ex index 4a2ede8..e87ae5f 100644 --- a/lib/polar_web/router.ex +++ b/lib/polar_web/router.ex @@ -77,6 +77,8 @@ defmodule PolarWeb.Router do scope "/spaces/:space_token", PolarWeb do pipe_through :api + get "/", SpaceController, :show + scope "/streams/v1" do get "/index.json", StreamController, :index get "/images.json", Streams.ImageController, :index diff --git a/test/polar_web/controllers/error_html_test.exs b/test/polar_web/controllers/error_html_test.exs index fc9cf51..33868a2 100644 --- a/test/polar_web/controllers/error_html_test.exs +++ b/test/polar_web/controllers/error_html_test.exs @@ -9,6 +9,6 @@ defmodule PolarWeb.ErrorHTMLTest do end test "renders 500.html" do - assert render_to_string(PolarWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" + assert render_to_string(PolarWeb.ErrorHTML, "500", "html", []) =~ "Something went wrong." end end diff --git a/test/polar_web/live/dashboard/credential/new_live_test.exs b/test/polar_web/live/dashboard/credential/new_live_test.exs new file mode 100644 index 0000000..e69de29 diff --git a/test/polar_web/live/dashboard/space/new_live_test.exs b/test/polar_web/live/dashboard/space/new_live_test.exs new file mode 100644 index 0000000..148cf2f --- /dev/null +++ b/test/polar_web/live/dashboard/space/new_live_test.exs @@ -0,0 +1,33 @@ +defmodule PolarWeb.Dashboard.Space.NewLiveTest do + use PolarWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + import Polar.AccountsFixtures + + alias Polar.Repo + alias Polar.Accounts.Space + + setup %{conn: conn} do + user = user_fixture() + + conn = log_in_user(conn, user) + + %{conn: conn} + end + + describe "New space creation" do + test "create space", %{conn: conn} do + {:ok, lv, _html} = live(conn, ~p"/dashboard/spaces/new") + + lv + |> form("#new-space-form", %{"space" => %{"name" => "some-test-space"}}) + |> render_submit() + + space = Repo.get_by!(Space, name: "some-test-space") + + %{"info" => flash} = assert_redirect(lv, ~p"/dashboard/spaces/#{space.id}") + + assert flash =~ "Space successfully created!" + end + end +end diff --git a/test/polar_web/live/dashboard/space_live_test.exs b/test/polar_web/live/dashboard/space_live_test.exs new file mode 100644 index 0000000..ccc300f --- /dev/null +++ b/test/polar_web/live/dashboard/space_live_test.exs @@ -0,0 +1,65 @@ +defmodule PolarWeb.Dashboard.SpaceLiveTest do + use PolarWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + import Polar.AccountsFixtures + + alias Polar.Accounts + + setup %{conn: conn} do + user = user_fixture() + + {:ok, space} = Accounts.create_space(user, %{name: "test-space-123"}) + + conn = log_in_user(conn, user) + + %{conn: conn, user: user, space: space} + end + + describe "Space page" do + test "render space detail", %{conn: conn, space: space} do + {:ok, _lv, html} = live(conn, ~p"/dashboard/spaces/#{space.id}") + + assert html =~ "No credential" + end + + test "create new credential", %{conn: conn, space: space} do + {:ok, lv, _html} = live(conn, ~p"/dashboard/spaces/#{space.id}") + + lv + |> element("a", "Create new credential") + |> render_click() + + assert_redirect(lv, ~p"/dashboard/spaces/#{space.id}/credentials/new") + end + end + + describe "with existing credential" do + setup %{space: space, user: user} do + {:ok, credential} = + Accounts.create_space_credential(space, user, %{ + expires_in: 1_296_000, + name: "test-cred", + type: "lxd" + }) + + %{credential: credential} + end + + test "can see existing credential", %{conn: conn, space: space, credential: credential} do + {:ok, _lv, html} = live(conn, ~p"/dashboard/spaces/#{space.id}") + + assert html =~ credential.name + end + + test "click on view credential", %{conn: conn, space: space, credential: credential} do + {:ok, lv, _html} = live(conn, ~p"/dashboard/spaces/#{space.id}") + + lv + |> element("#credential_#{credential.id} a", "View credential") + |> render_click() + + assert_redirect(lv, ~p"/dashboard/spaces/#{space.id}/credentials/#{credential.id}") + end + end +end diff --git a/test/polar_web/live/dashboard_live_test.exs b/test/polar_web/live/dashboard_live_test.exs new file mode 100644 index 0000000..f10ccdb --- /dev/null +++ b/test/polar_web/live/dashboard_live_test.exs @@ -0,0 +1,81 @@ +defmodule PolarWeb.DashboardLiveTest do + use PolarWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + import Polar.AccountsFixtures + + alias Polar.Accounts + + setup do + user = user_fixture() + + %{user: user} + end + + describe "Dashboard page" do + test "render dashboard", %{conn: conn, user: user} do + {:ok, _lv, html} = + conn + |> log_in_user(user) + |> live(~p"/dashboard") + + assert html =~ "Your spaces" + assert html =~ "Create new space" + end + + test "redirects if user is not logged in", %{conn: conn} do + assert {:error, redirect} = live(conn, ~p"/dashboard") + + assert {:redirect, %{to: path, flash: _flash}} = redirect + assert path == ~p"/users/log_in" + end + end + + describe "new space" do + setup %{conn: conn} do + user = user_fixture() + + conn = log_in_user(conn, user) + + %{user: user, conn: conn} + end + + test "creates a new space", %{conn: conn} do + {:ok, lv, _html} = live(conn, ~p"/dashboard") + + lv + |> element("a", "Create new space") + |> render_click() + + assert_redirect(lv, ~p"/dashboard/spaces/new") + end + end + + describe "with existing space" do + setup %{conn: conn} do + user = user_fixture() + + {:ok, space} = Accounts.create_space(user, %{name: "example-test-space"}) + + conn = log_in_user(conn, user) + + %{space: space, conn: conn} + end + + test "can see existing space", %{conn: conn, space: space} do + {:ok, _lv, html} = live(conn, ~p"/dashboard") + + assert html =~ space.name + end + + test "can click into space", %{conn: conn, space: space} do + {:ok, lv, _html} = live(conn, ~p"/dashboard") + + lv + |> element("#space_#{space.id} a", "View space") + |> render_click() + + assert_redirect(lv, ~p"/dashboard/spaces/#{space.id}") + end + end +end From 4bb0bcae68a9d0ce6452ee43932715a8b5650cbd Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Tue, 27 Feb 2024 19:33:43 +0700 Subject: [PATCH 8/9] Add test for new credential and credential live --- .../dashboard/credential/new_live_test.exs | 36 +++++++++++++++++++ .../live/dashboard/credential_live_test.exs | 34 ++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/polar_web/live/dashboard/credential_live_test.exs diff --git a/test/polar_web/live/dashboard/credential/new_live_test.exs b/test/polar_web/live/dashboard/credential/new_live_test.exs index e69de29..c7265f6 100644 --- a/test/polar_web/live/dashboard/credential/new_live_test.exs +++ b/test/polar_web/live/dashboard/credential/new_live_test.exs @@ -0,0 +1,36 @@ +defmodule PolarWeb.Dashboard.Credential.NewLiveTest do + use PolarWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + import Polar.AccountsFixtures + + alias Polar.Repo + alias Polar.Accounts + alias Polar.Accounts.Space + + setup %{conn: conn} do + user = user_fixture() + + conn = log_in_user(conn, user) + + {:ok, space} = Accounts.create_space(user, %{name: "test-space-456"}) + + %{conn: conn, space: space} + end + + describe "New credential creation" do + test "create credential", %{conn: conn, space: space} do + {:ok, lv, _html} = live(conn, ~p"/dashboard/spaces/#{space.id}/credentials/new") + + lv + |> form("#new-credential-form", %{ + "credential" => %{"name" => "new-cred-test", "type" => "lxd", "expires_in" => "2592000"} + }) + |> render_submit() + + credential = Repo.get_by!(Space.Credential, name: "new-cred-test") + + assert_redirect(lv, ~p"/dashboard/spaces/#{space.id}/credentials/#{credential.id}") + end + end +end diff --git a/test/polar_web/live/dashboard/credential_live_test.exs b/test/polar_web/live/dashboard/credential_live_test.exs new file mode 100644 index 0000000..6862c00 --- /dev/null +++ b/test/polar_web/live/dashboard/credential_live_test.exs @@ -0,0 +1,34 @@ +defmodule PolarWeb.Dashboard.CredentialLiveTest do + use PolarWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + import Polar.AccountsFixtures + + alias Polar.Accounts + + setup %{conn: conn} do + user = user_fixture() + + {:ok, space} = Accounts.create_space(user, %{name: "test-space-123"}) + + {:ok, credential} = + Accounts.create_space_credential(space, user, %{ + expires_in: 1_296_000, + name: "test-cred", + type: "lxd" + }) + + conn = log_in_user(conn, user) + + %{conn: conn, user: user, space: space, credential: credential} + end + + describe "credential detail" do + test "can see detail of credential", %{conn: conn, space: space, credential: credential} do + {:ok, _lv, html} = + live(conn, ~p"/dashboard/spaces/#{space.id}/credentials/#{credential.id}") + + assert html =~ "lxc remote add opsmaru" + end + end +end From 62921c6ab0a80235e692ba011f4962c159e4e8df Mon Sep 17 00:00:00 2001 From: Zack Siri Date: Tue, 27 Feb 2024 19:37:06 +0700 Subject: [PATCH 9/9] Add test for field validation --- .../live/dashboard/credential/new_live_test.exs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/polar_web/live/dashboard/credential/new_live_test.exs b/test/polar_web/live/dashboard/credential/new_live_test.exs index c7265f6..6361780 100644 --- a/test/polar_web/live/dashboard/credential/new_live_test.exs +++ b/test/polar_web/live/dashboard/credential/new_live_test.exs @@ -32,5 +32,17 @@ defmodule PolarWeb.Dashboard.Credential.NewLiveTest do assert_redirect(lv, ~p"/dashboard/spaces/#{space.id}/credentials/#{credential.id}") end + + test "validation", %{conn: conn, space: space} do + {:ok, lv, _html} = live(conn, ~p"/dashboard/spaces/#{space.id}/credentials/new") + + lv + |> form("#new-credential-form") + |> render_change(%{ + "credential" => %{"name" => "new-cred-test"} + }) + + assert render(lv) =~ "can't be blank" + end end end