Make online store templates searchable
This commit is contained in:
@@ -23,6 +23,37 @@ defmodule MusicLibrary.OnlineStoreTemplates do
|
|||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@type list_opts :: [query: String.t(), offset: non_neg_integer(), limit: non_neg_integer()]
|
||||||
|
|
||||||
|
@spec list_templates(list_opts()) :: [OnlineStoreTemplate.t()]
|
||||||
|
def list_templates(opts) do
|
||||||
|
query =
|
||||||
|
OnlineStoreTemplate
|
||||||
|
|> order_by([t], fragment("? COLLATE NOCASE ASC", t.name))
|
||||||
|
|> filter_templates(opts)
|
||||||
|
|
||||||
|
query =
|
||||||
|
case Keyword.get(opts, :offset) do
|
||||||
|
nil -> query
|
||||||
|
offset -> from(t in query, offset: ^offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
query =
|
||||||
|
case Keyword.get(opts, :limit) do
|
||||||
|
nil -> query
|
||||||
|
limit -> from(t in query, limit: ^limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec count_templates(list_opts()) :: non_neg_integer()
|
||||||
|
def count_templates(opts \\ []) do
|
||||||
|
OnlineStoreTemplate
|
||||||
|
|> filter_templates(opts)
|
||||||
|
|> Repo.aggregate(:count)
|
||||||
|
end
|
||||||
|
|
||||||
@spec get_template!(String.t()) :: OnlineStoreTemplate.t()
|
@spec get_template!(String.t()) :: OnlineStoreTemplate.t()
|
||||||
def get_template!(id), do: Repo.get!(OnlineStoreTemplate, id)
|
def get_template!(id), do: Repo.get!(OnlineStoreTemplate, id)
|
||||||
|
|
||||||
@@ -62,4 +93,17 @@ defmodule MusicLibrary.OnlineStoreTemplates do
|
|||||||
|> String.replace("{title}", URI.encode_www_form(record.title))
|
|> String.replace("{title}", URI.encode_www_form(record.title))
|
||||||
|> String.replace("{format}", URI.encode_www_form(format_string))
|
|> String.replace("{format}", URI.encode_www_form(format_string))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp filter_templates(query, opts) do
|
||||||
|
case Keyword.get(opts, :query) do
|
||||||
|
q when q in [nil, ""] ->
|
||||||
|
query
|
||||||
|
|
||||||
|
q ->
|
||||||
|
like = "%#{q}%"
|
||||||
|
|
||||||
|
from t in query,
|
||||||
|
where: like(t.name, ^like) or like(t.description, ^like)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
||||||
use MusicLibraryWeb, :live_view
|
use MusicLibraryWeb, :live_view
|
||||||
|
|
||||||
|
import MusicLibraryWeb.Components.Pagination
|
||||||
|
import MusicLibraryWeb.LiveHelpers.Params
|
||||||
|
|
||||||
alias MusicLibrary.OnlineStoreTemplates
|
alias MusicLibrary.OnlineStoreTemplates
|
||||||
alias MusicLibrary.OnlineStoreTemplates.OnlineStoreTemplate
|
alias MusicLibrary.OnlineStoreTemplates.OnlineStoreTemplate
|
||||||
|
|
||||||
|
@default_list_params %{
|
||||||
|
page: 1,
|
||||||
|
page_size: 50,
|
||||||
|
query: ""
|
||||||
|
}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<Layouts.app flash={@flash} current_section={@current_section} socket={@socket}>
|
<Layouts.app flash={@flash} current_section={@current_section} socket={@socket}>
|
||||||
<header class="mb-6">
|
<header class="gap-6 mb-2">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between gap-6 mb-2 mt-2">
|
||||||
<div>
|
<.search_form query={@list_params.query} />
|
||||||
<h1 class="text-2xl font-bold text-zinc-900 dark:text-zinc-100">
|
<.button variant="solid" size="sm" patch={~p"/online-store-templates/new"}>
|
||||||
{gettext("Online Store Templates")}
|
<.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" />
|
||||||
</h1>
|
{gettext("Add")}
|
||||||
</div>
|
</.button>
|
||||||
<div>
|
|
||||||
<.button variant="solid" size="sm" patch={~p"/online-store-templates/new"}>
|
|
||||||
<.icon name="hero-plus" class="icon" aria-hidden="true" data-slot="icon" />
|
|
||||||
{gettext("Add")}
|
|
||||||
</.button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -31,6 +34,16 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
phx-update="stream"
|
phx-update="stream"
|
||||||
id="templates"
|
id="templates"
|
||||||
>
|
>
|
||||||
|
<li
|
||||||
|
id="no-templates"
|
||||||
|
class="hidden only:block p-8 text-center bg-zinc-50 dark:bg-zinc-800 rounded-lg"
|
||||||
|
>
|
||||||
|
<.icon name="hero-building-storefront" class="h-12 w-12 text-zinc-400 mx-auto mb-4" />
|
||||||
|
<p class="text-zinc-600 dark:text-zinc-400">
|
||||||
|
{gettext("No online store templates found")}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
:for={{id, template} <- @streams.templates}
|
:for={{id, template} <- @streams.templates}
|
||||||
id={id}
|
id={id}
|
||||||
@@ -98,12 +111,14 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<.pagination id={:bottom_pagination} pagination_params={@list_params} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<.structured_modal
|
<.structured_modal
|
||||||
:if={@live_action in [:new, :edit]}
|
:if={@live_action in [:new, :edit]}
|
||||||
id="template-modal"
|
id="template-modal"
|
||||||
on_close={JS.patch(~p"/online-store-templates")}
|
on_close={JS.patch(back_path(@list_params))}
|
||||||
>
|
>
|
||||||
<.live_component
|
<.live_component
|
||||||
module={MusicLibraryWeb.OnlineStoreTemplateLive.Form}
|
module={MusicLibraryWeb.OnlineStoreTemplateLive.Form}
|
||||||
@@ -111,7 +126,7 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
title={@page_title}
|
title={@page_title}
|
||||||
action={@live_action}
|
action={@live_action}
|
||||||
template={@template}
|
template={@template}
|
||||||
patch={~p"/online-store-templates"}
|
patch={back_path(@list_params)}
|
||||||
/>
|
/>
|
||||||
</.structured_modal>
|
</.structured_modal>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
@@ -120,10 +135,7 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
{:ok,
|
{:ok, assign(socket, :current_section, :online_store_templates)}
|
||||||
socket
|
|
||||||
|> assign(:current_section, :online_store_templates)
|
|
||||||
|> stream(:templates, OnlineStoreTemplates.list_templates())}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -131,22 +143,56 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
defp apply_action(socket, :edit, %{"id" => id} = params) do
|
||||||
socket
|
socket
|
||||||
|
|> apply_fallback_index(params, :templates, &apply_action/3)
|
||||||
|> assign(:page_title, gettext("Edit Online Store Template"))
|
|> assign(:page_title, gettext("Edit Online Store Template"))
|
||||||
|> assign(:template, OnlineStoreTemplates.get_template!(id))
|
|> assign(:template, OnlineStoreTemplates.get_template!(id))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :new, _params) do
|
defp apply_action(socket, :new, params) do
|
||||||
socket
|
socket
|
||||||
|
|> apply_fallback_index(params, :templates, &apply_action/3)
|
||||||
|> assign(:page_title, gettext("New Online Store Template"))
|
|> assign(:page_title, gettext("New Online Store Template"))
|
||||||
|> assign(:template, %OnlineStoreTemplate{})
|
|> assign(:template, %OnlineStoreTemplate{})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :index, _params) do
|
defp apply_action(socket, :index, params) do
|
||||||
|
query = params["query"]
|
||||||
|
total_templates = OnlineStoreTemplates.count_templates(query: query)
|
||||||
|
|
||||||
|
list_params =
|
||||||
|
@default_list_params
|
||||||
|
|> merge_query(query)
|
||||||
|
|> merge_pagination(params, total_templates)
|
||||||
|
|
||||||
|
load_and_assign_templates(socket, list_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp load_and_assign_templates(socket, list_params) do
|
||||||
|
offset = page_to_offset(list_params.page, list_params.page_size)
|
||||||
|
|
||||||
|
templates =
|
||||||
|
OnlineStoreTemplates.list_templates(
|
||||||
|
query: list_params.query,
|
||||||
|
offset: offset,
|
||||||
|
limit: list_params.page_size
|
||||||
|
)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|
|> assign(:list_params, list_params)
|
||||||
|> assign(:page_title, gettext("Online Store Templates"))
|
|> assign(:page_title, gettext("Online Store Templates"))
|
||||||
|> assign(:template, nil)
|
|> assign(:template, nil)
|
||||||
|
|> stream(:templates, templates, reset: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp back_path(list_params) do
|
||||||
|
qs =
|
||||||
|
list_params
|
||||||
|
|> Map.take([:page, :page_size, :query])
|
||||||
|
|> Enum.filter(fn {_, v} -> v not in ["", nil] end)
|
||||||
|
|
||||||
|
~p"/online-store-templates?#{qs}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -154,7 +200,10 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
{MusicLibraryWeb.OnlineStoreTemplateLive.Form, {:saved, template}},
|
{MusicLibraryWeb.OnlineStoreTemplateLive.Form, {:saved, template}},
|
||||||
socket
|
socket
|
||||||
) do
|
) do
|
||||||
{:noreply, stream_insert(socket, :templates, template)}
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> stream_insert(:templates, template)
|
||||||
|
|> load_and_assign_templates(socket.assigns.list_params)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -162,7 +211,10 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
template = OnlineStoreTemplates.get_template!(id)
|
template = OnlineStoreTemplates.get_template!(id)
|
||||||
{:ok, _} = OnlineStoreTemplates.delete_template(template)
|
{:ok, _} = OnlineStoreTemplates.delete_template(template)
|
||||||
|
|
||||||
{:noreply, stream_delete(socket, :templates, template)}
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> stream_delete(:templates, template)
|
||||||
|
|> load_and_assign_templates(socket.assigns.list_params)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@@ -174,4 +226,13 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.Index do
|
|||||||
|
|
||||||
{:noreply, stream_insert(socket, :templates, updated_template)}
|
{:noreply, stream_insert(socket, :templates, updated_template)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("search", %{"query" => query}, socket) do
|
||||||
|
qs =
|
||||||
|
@default_list_params
|
||||||
|
|> Map.put(:query, query)
|
||||||
|
|> Map.take([:query, :page, :page_size])
|
||||||
|
|
||||||
|
{:noreply, push_patch(socket, to: ~p"/online-store-templates?#{qs}")}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2303,3 +2303,8 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "New Online Store Template"
|
msgid "New Online Store Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "No online store templates found"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -2303,3 +2303,8 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "New Online Store Template"
|
msgid "New Online Store Template"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/live/online_store_template_live/index.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "No online store templates found"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -28,6 +28,49 @@ defmodule MusicLibrary.OnlineStoreTemplatesTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "list_templates/1" do
|
||||||
|
test "filters by name" do
|
||||||
|
online_store_template(%{name: "Amazon UK"})
|
||||||
|
online_store_template(%{name: "Bandcamp"})
|
||||||
|
|
||||||
|
results = OnlineStoreTemplates.list_templates(query: "Amazon")
|
||||||
|
assert length(results) == 1
|
||||||
|
assert hd(results).name == "Amazon UK"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "filters by description" do
|
||||||
|
online_store_template(%{name: "Store A", description: "vinyl marketplace"})
|
||||||
|
online_store_template(%{name: "Store B", description: "digital downloads"})
|
||||||
|
|
||||||
|
results = OnlineStoreTemplates.list_templates(query: "vinyl")
|
||||||
|
assert length(results) == 1
|
||||||
|
assert hd(results).name == "Store A"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "respects offset and limit" do
|
||||||
|
for i <- 1..3, do: online_store_template(%{name: "Store #{i}"})
|
||||||
|
|
||||||
|
results = OnlineStoreTemplates.list_templates(limit: 1)
|
||||||
|
assert length(results) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "count_templates/1" do
|
||||||
|
test "counts all templates" do
|
||||||
|
online_store_template(%{name: "Amazon"})
|
||||||
|
online_store_template(%{name: "Bandcamp"})
|
||||||
|
|
||||||
|
assert OnlineStoreTemplates.count_templates() == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "counts filtered templates" do
|
||||||
|
online_store_template(%{name: "Amazon UK"})
|
||||||
|
online_store_template(%{name: "Bandcamp"})
|
||||||
|
|
||||||
|
assert OnlineStoreTemplates.count_templates(query: "Amazon") == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "create_template/1" do
|
describe "create_template/1" do
|
||||||
test "creates with valid attrs" do
|
test "creates with valid attrs" do
|
||||||
assert {:ok, template} =
|
assert {:ok, template} =
|
||||||
|
|||||||
@@ -11,10 +11,28 @@ defmodule MusicLibraryWeb.OnlineStoreTemplateLive.IndexTest do
|
|||||||
|
|
||||||
conn
|
conn
|
||||||
|> visit(~p"/online-store-templates")
|
|> visit(~p"/online-store-templates")
|
||||||
|> assert_has("h1", "Online Store Templates")
|
|
||||||
|> assert_has("p", "Amazon UK")
|
|> assert_has("p", "Amazon UK")
|
||||||
|> assert_has("p", "Bandcamp")
|
|> assert_has("p", "Bandcamp")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "search filters templates", %{conn: conn} do
|
||||||
|
online_store_template(%{name: "Amazon UK"})
|
||||||
|
online_store_template(%{name: "Bandcamp"})
|
||||||
|
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/online-store-templates")
|
||||||
|
|
||||||
|
assert has_element?(view, "p", "Amazon UK")
|
||||||
|
assert has_element?(view, "p", "Bandcamp")
|
||||||
|
|
||||||
|
view
|
||||||
|
|> form("form", query: "Amazon")
|
||||||
|
|> render_change()
|
||||||
|
|
||||||
|
assert_patch(view, ~p"/online-store-templates?page=1&page_size=50&query=Amazon")
|
||||||
|
|
||||||
|
assert has_element?(view, "p", "Amazon UK")
|
||||||
|
refute has_element?(view, "p", "Bandcamp")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Create template" do
|
describe "Create template" do
|
||||||
|
|||||||
Reference in New Issue
Block a user