31 Commits
Author SHA1 Message Date
KKlochko 045faf59a4 Update the CI/CD configuration to build the image.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-05-27 21:44:40 +03:00
KKlochko 152bee732f Update the version. 2025-05-27 21:43:51 +03:00
KKlochko 99188454e9 Update Navbar partial to show the Server menu item for moderators.
continuous-integration/drone/push Build is passing
2025-05-26 20:38:19 +03:00
KKlochko 80e99b7de8 Remove extra debug logs which used to inspect a state.
continuous-integration/drone/push Build is passing
2025-05-26 14:30:53 +03:00
KKlochko 51c584056b Fix a typo in SearchResources component that an admin is a moderator.
continuous-integration/drone/push Build is passing
2025-05-26 14:21:59 +03:00
KKlochko b70b99ba51 Update DBIServer's Index and Show to allow moderators to read. 2025-05-26 14:21:14 +03:00
KKlochko 9c7a555dae Update Show LiveViews to add information from which server. 2025-05-26 14:19:38 +03:00
KKlochko ab3d65a899 Update Oban configuration to prune jobs which older than 7 days.
continuous-integration/drone/push Build is passing
2025-05-25 21:26:22 +03:00
KKlochko 44841a53e8 Update the LiveViews permission test to fix misuse of the user values.
continuous-integration/drone/push Build is passing
2025-05-24 21:56:49 +03:00
KKlochko a25a4e24da Update to refatactor tests for syncing. 2025-05-24 21:52:22 +03:00
KKlochko 493aca0e5b Update to move get_id functions to support/helpers. 2025-05-24 21:46:02 +03:00
KKlochko 8a3b522049 Update to refactor tests for Author and Book. 2025-05-24 21:21:04 +03:00
KKlochko 89c871110c Update to refactor tests for resource forms. 2025-05-24 21:19:19 +03:00
KKlochko 64028d09f5 Fix data transformers to remove a nil value of dbi_server for syncing.
continuous-integration/drone/push Build is passing
Map.put_new don't update nil value, because the key exists.
2025-05-23 12:50:43 +03:00
KKlochko ea5ada751d Update DBIServerTransformer to fetch the dbi_server relationship. 2025-05-23 12:47:46 +03:00
KKlochko f4eb929401 Update DBIServer to make the relationship to another server public. 2025-05-23 12:42:39 +03:00
KKlochko 59e2d2f8a1 Update the footer to add the license.
continuous-integration/drone/push Build is passing
2025-05-22 21:38:29 +03:00
KKlochko ddcab0b843 Update the API get actions to use the by_id action.
continuous-integration/drone/push Build is passing
2025-05-21 19:42:13 +03:00
KKlochko b7979bf6ba Add descriptions for resources' actions. 2025-05-21 19:41:28 +03:00
KKlochko db025ece6a Fix DBIServer FormComponent to allow edit only other servers' sync_on?.
continuous-integration/drone/push Build is passing
2025-05-20 22:13:10 +03:00
KKlochko ffbec934a6 Update resources and generator to accept dbi_server_id.
continuous-integration/drone/push Build is passing
2025-05-19 22:09:22 +03:00
KKlochko 4edf526475 Update tests to add missing assert and = which check permission for LiveViews.
continuous-integration/drone/push Build is failing
2025-05-18 22:22:01 +03:00
KKlochko 4a28708479 Add tests to check permissions for editing other servers' data. 2025-05-18 22:19:11 +03:00
KKlochko 3430693dac Update the pagination component to show itself if there's more than one page.
continuous-integration/drone/push Build is failing
2025-05-17 22:10:59 +03:00
KKlochko 54f15bead3 Rename the test module to LiveViewsPermissionsTest. 2025-05-17 22:09:54 +03:00
KKlochko 88a8eba322 Update to refactor tests which check permission for LiveViews.
continuous-integration/drone/push Build is passing
2025-05-16 13:10:25 +03:00
KKlochko f406239b16 Add tests to check permission for Show and Edit LiveViews.
continuous-integration/drone/push Build is passing
2025-05-15 11:50:33 +03:00
KKlochko bdc6670475 Add tests to check permission for Index LiveViews.
continuous-integration/drone/push Build is passing
2025-05-14 14:43:15 +03:00
KKlochko 4688d09dcf Add the LiveCase template for testing LiveViews. 2025-05-14 14:42:47 +03:00
KKlochko c51bca1eb5 Add the helper to make a user log in. 2025-05-14 14:41:42 +03:00
KKlochko 7bf009c38f Update the error message for the RequireAdmin Plug. 2025-05-14 14:41:10 +03:00
51 changed files with 1230 additions and 229 deletions
+14
View File
@@ -24,6 +24,20 @@ steps:
commands:
- mix test
- name: build the image
image: plugins/kaniko
settings:
username: kklochko
password:
from_secret: REGISTRY_PASSWORD
repo: git.kklochko.space/kklochko/decentralised_book_index
registry: git.kklochko.space
tags:
- latest
- ${DRONE_TAG}
when:
event: tag
volumes:
- name: mix
temp: {}
+2 -1
View File
@@ -12,7 +12,8 @@ config :decentralised_book_index, Oban,
repo: DecentralisedBookIndex.Repo,
queues: [default: 14],
plugins: [
Oban.Plugins.Pruner,
# A job will be pruned after 7 days.
{Oban.Plugins.Pruner, max_age: 7*24*60*60},
{Oban.Plugins.Cron,
crontab: [
{"@daily", DecentralisedBookIndex.SyncWorker},
+4 -4
View File
@@ -6,7 +6,7 @@ defmodule DecentralisedBookIndex.Metadata do
json_api do
routes do
base_route "/books", Metadata.Book do
get :read
get :by_id
index :search
related :bids, :read, primary?: true
@@ -15,17 +15,17 @@ defmodule DecentralisedBookIndex.Metadata do
end
base_route "/authors", Metadata.Author do
get :read
get :by_id
index :search
end
base_route "/publishers", Metadata.Publisher do
get :read
get :by_id
index :search
end
base_route "/servers", Metadata.DBIServer do
get :read
get :by_id
index :search
end
end
@@ -25,12 +25,16 @@ defmodule DecentralisedBookIndex.Metadata.Author do
end
end
resource do
description "An author's metadata."
end
actions do
defaults [:read, :destroy]
create :create do
primary? true
accept [:name, :description, :avatar_url, :author_alias_registry_id]
accept [:name, :description, :avatar_url, :author_alias_registry_id, :dbi_server_id]
change fn changeset, context ->
actor = Map.get(context, :actor, nil)
@@ -100,6 +104,7 @@ defmodule DecentralisedBookIndex.Metadata.Author do
end
read :by_id do
description "Return the Author by its id."
argument :id, :uuid, allow_nil?: false
get? true
filter expr(id == ^arg(:id))
@@ -136,7 +141,10 @@ defmodule DecentralisedBookIndex.Metadata.Author do
end
read :search do
description "Return a list of Authors, optionally filtering by name."
argument :name, :ci_string do
description "Return Authors, which names includes the name."
constraints allow_empty?: true
default ""
end
@@ -21,8 +21,12 @@ defmodule DecentralisedBookIndex.Metadata.AuthorRole do
end
end
resource do
description "A metadata of an author's role."
end
actions do
defaults [:read, :destroy]
defaults [:destroy]
create :create do
primary? true
@@ -43,13 +47,20 @@ defmodule DecentralisedBookIndex.Metadata.AuthorRole do
end
end
read :read do
description "Return a list of AuthorRoles."
primary? true
end
read :by_id do
description "Return the AuthorRole by its id."
argument :id, :uuid, allow_nil?: false
get? true
filter expr(id == ^arg(:id))
end
read :by_book_id do
description "Return the AuthorRole by its BookId."
argument :book_id, :uuid, allow_nil?: false
filter expr(book_id == ^arg(:book_id))
end
@@ -25,6 +25,10 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end
end
resource do
description "A book's metadata."
end
actions do
defaults [:read, :destroy]
@@ -142,6 +146,7 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end
read :by_id do
description "Return the Book by its id."
argument :id, :uuid, allow_nil?: false
get? true
filter expr(id == ^arg(:id))
@@ -182,7 +187,10 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end
read :search do
description "Return a list of Books, optionally filtering by title."
argument :query, :ci_string do
description "Return Books, which titles includes the query."
constraints allow_empty?: true
default ""
end
@@ -193,6 +201,8 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end
read :search_by_bid do
description "Return a list of Books, optionally filtering by BookId."
argument :type, :string, allow_nil?: false
argument :bid, :string, allow_nil?: false
@@ -26,8 +26,12 @@ defmodule DecentralisedBookIndex.Metadata.BookId do
end
end
resource do
description "A book's identifier."
end
actions do
defaults [:read, :destroy]
defaults [:destroy]
create :create do
primary? true
@@ -39,7 +43,13 @@ defmodule DecentralisedBookIndex.Metadata.BookId do
accept [:id, :type, :bid, :order]
end
read :read do
description "Return a list of BookIds."
primary? true
end
read :by_id do
description "Return the BookId by its id."
argument :id, :uuid, allow_nil?: false
get? true
filter expr(id == ^arg(:id))
@@ -21,12 +21,16 @@ defmodule DecentralisedBookIndex.Metadata.DBIServer do
end
end
resource do
description "An DBIServer's metadata."
end
actions do
defaults [:read, :destroy]
create :create do
primary? true
accept [:name, :url, :sync_on?]
accept [:name, :url, :sync_on?, :dbi_server_id]
end
create :sync_create do
@@ -43,13 +47,17 @@ defmodule DecentralisedBookIndex.Metadata.DBIServer do
end
read :by_id do
description "Return the DBIServer by its id."
argument :id, :uuid, allow_nil?: false
get? true
filter expr(id == ^arg(:id))
end
read :search do
description "Return a list of DBIServers, optionally filtering by name."
argument :name, :ci_string do
description "Return DBIServers, which names includes the name."
constraints allow_empty?: true
default ""
end
@@ -95,7 +103,9 @@ defmodule DecentralisedBookIndex.Metadata.DBIServer do
end
relationships do
belongs_to :dbi_server, Metadata.DBIServer
belongs_to :dbi_server, Metadata.DBIServer do
public? true
end
has_many :author, Metadata.Author
has_many :author_alias_registries, Metadata.AuthorAliasRegistry
@@ -22,26 +22,39 @@ defmodule DecentralisedBookIndex.Metadata.Publisher do
end
end
resource do
description "An publisher's metadata."
end
actions do
defaults [:read, :destroy]
defaults [:destroy]
create :create do
primary? true
accept [:name]
accept [:name, :dbi_server_id]
end
create :sync_create do
accept [:id, :name, :inserted_at, :updated_at, :dbi_server_id]
end
read :read do
description "Return a list of Publishers."
primary? true
end
read :by_id do
description "Return the Publisher by its id."
argument :id, :uuid, allow_nil?: false
get? true
filter expr(id == ^arg(:id))
end
read :search do
description "Return a list of Publishers, optionally filtering by name."
argument :name, :ci_string do
description "Return Publishers, which names includes the name."
constraints allow_empty?: true
default ""
end
@@ -20,6 +20,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorTransformer do
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"])
}
{:ok, attrs}
if is_nil(attrs[:dbi_server_id]) do
{:ok, attrs |> Map.delete(:dbi_server_id)}
else
{:ok, attrs}
end
end
end
@@ -26,6 +26,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookTransformer do
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"])
}
{:ok, attrs}
if is_nil(attrs[:dbi_server_id]) do
{:ok, attrs |> Map.delete(:dbi_server_id)}
else
{:ok, attrs}
end
end
end
@@ -13,9 +13,15 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformer do
name: get_in(json_body, ["attributes", "name"]),
url: get_in(json_body, ["attributes", "url"]),
inserted_at: get_in(json_body, ["attributes", "inserted_at"]),
updated_at: get_in(json_body, ["attributes", "updated_at"])
updated_at: get_in(json_body, ["attributes", "updated_at"]),
# relationship
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"])
}
{:ok, attrs}
if is_nil(attrs[:dbi_server_id]) do
{:ok, attrs |> Map.delete(:dbi_server_id)}
else
{:ok, attrs}
end
end
end
@@ -17,6 +17,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.PublisherTransformer do
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"])
}
{:ok, attrs}
if is_nil(attrs[:dbi_server_id]) do
{:ok, attrs |> Map.delete(:dbi_server_id)}
else
{:ok, attrs}
end
end
end
@@ -11,48 +11,58 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Pagination do
assigns = assign(assigns, :current_page_number, get_current_page(assigns.page))
~H"""
<nav aria-label="Page navigation example" class="flex justify-center">
<ul class="inline-flex -space-x-px text-base h-10 mx-auto">
<li class="pl-auto">
<.button_link
data-role="previous-page"
kind="primary"
inverse
patch={"#{@endpoint}?#{page_params_which(@page, @params, "prev")}"}
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
disabled={!AshPhoenix.LiveView.prev_page?(@page)}
>
Previous
</.button_link>
</li>
<%= for number <- get_page_numbers(@page) do %>
<%= if number == @current_page_number do %>
<li>
<a href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
aria-current="page"
class="flex items-center justify-center px-4 h-10 text-blue-600 border border-gray-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white">{number}</a>
</li>
<% else %>
<li>
<a href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{number}</a>
</li>
<%= if AshPhoenix.LiveView.prev_page?(@page) or AshPhoenix.LiveView.next_page?(@page) do %>
<nav aria-label="Page navigation example" class="flex justify-center">
<ul class="inline-flex -space-x-px text-base h-10 mx-auto">
<li class="pl-auto">
<.button_link
data-role="previous-page"
kind="primary"
inverse
patch={"#{@endpoint}?#{page_params_which(@page, @params, "prev")}"}
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
disabled={!AshPhoenix.LiveView.prev_page?(@page)}
>
Previous
</.button_link>
</li>
<%= for number <- get_page_numbers(@page) do %>
<%= if number == @current_page_number do %>
<li>
<a
href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
aria-current="page"
class="flex items-center justify-center px-4 h-10 text-blue-600 border border-gray-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white"
>
{number}
</a>
</li>
<% else %>
<li>
<a
href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
>
{number}
</a>
</li>
<% end %>
<% end %>
<% end %>
<li>
<.button_link
data-role="next-page"
kind="primary"
inverse
patch={"#{@endpoint}?#{page_params_which(@page, @params, "next")}"}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
disabled={!AshPhoenix.LiveView.next_page?(@page)}
>
Next
</.button_link>
</li>
</ul>
</nav>
<li>
<.button_link
data-role="next-page"
kind="primary"
inverse
patch={"#{@endpoint}?#{page_params_which(@page, @params, "next")}"}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
disabled={!AshPhoenix.LiveView.next_page?(@page)}
>
Next
</.button_link>
</li>
</ul>
</nav>
<% end %>
"""
end
@@ -64,7 +64,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SearchResources do
defp can_add?(current_user, role) do
case role do
:admin -> current_user != nil and Role.can_moderate?(current_user.role)
:admin -> current_user != nil and Role.can_administrate?(current_user.role)
:moderator -> current_user != nil and Role.can_moderate?(current_user.role)
_ -> false
end
@@ -20,8 +20,6 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SelectedAuthor do
errors =
if Phoenix.Component.used_input?(assigns.author_form), do: assigns.author_form.errors, else: []
IO.inspect("errors")
IO.inspect(errors)
assigns =
assigns
@@ -9,6 +9,9 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Footer do
<span class="block text-sm text-gray-500 text-center dark:text-gray-400">
© 2025 Kostiantyn Klochko. All Rights Reserved.
</span>
<span class="block text-sm text-gray-500 text-center dark:text-gray-400">
<a href="https://git.kklochko.space/KKlochko/decentralised_book_index">Decentralised book index</a> is free software: you can redistribute it and/or modify it under the terms of <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">the GNU Affero General Public License</a> as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
</span>
</div>
</footer>
"""
@@ -106,7 +106,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Navbar do
</a>
</li>
<% end %>
<%= if @current_user != nil and Role.can_administrate?(@current_user.role) do %>
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) do %>
<li>
<a
href={~p"/servers/"}
@@ -158,9 +158,6 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.FormComponent do
end
def handle_event("select-author-alias", %{"author-alias" => author_alias_id}, socket) do
IO.inspect("book_editions_registry_id")
IO.inspect(author_alias_id)
socket =
socket
|> update(:form, fn form ->
@@ -57,6 +57,25 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
<p class="text-gray-300 leading-relaxed">
{@author.description}
</p>
<%= if not is_nil(@author.dbi_server) do %>
<dl>
<dt class="mt-4 mb-2 font-semibold leading-none text-gray-900 dark:text-white">
From Server
</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) do %>
<.link navigate={~p"/servers/#{@author.dbi_server.id}/"} class="hover:underline">
{@author.dbi_server.name}
</.link>
<% else %>
<.link navigate={@author.dbi_server.url} class="hover:underline">
{@author.dbi_server.name}
</.link>
<% end %>
</dd>
</dl>
<% end %>
</div>
</div>
@@ -74,6 +74,22 @@ defmodule DecentralisedBookIndexWeb.BookLive.Show do
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{bid.type}: {bid.bid}</dd>
<% end %>
</dl>
<%= if not is_nil(@book.dbi_server) do %>
<dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">From Server</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) do %>
<.link navigate={~p"/servers/#{@book.dbi_server.id}/"} class="hover:underline">
{@book.dbi_server.name}
</.link>
<% else %>
<.link navigate={@book.dbi_server.url} class="hover:underline">
{@book.dbi_server.name}
</.link>
<% end %>
</dd>
</dl>
<% end %>
</div>
<%= if not Enum.empty?(@alternative_editions) do %>
@@ -20,7 +20,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.FormComponent do
<.input field={@form[:name]} type="text" label="Name" />
<.input field={@form[:url]} type="text" label="Url" />
<% end %>
<%= if @form.source.type == :update and is_nil(@form[:dbi_server].value) do %>
<%= if @form.source.type == :update and is_nil(@form[:dbi_server_id].value) do %>
<.input field={@form[:name]} type="text" label="Name" />
<.input field={@form[:url]} type="text" label="Url" />
<% end %>
@@ -61,7 +61,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.FormComponent do
socket =
socket
|> put_flash(:info, "Server #{socket.assigns.form.source.type}d successfully")
|> push_navigate(to: patch_url(socket.assigns.action, dbi_server.id))
|> redirect(to: patch_url(socket.assigns.action, dbi_server.id))
{:noreply, socket}
@@ -4,26 +4,30 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Index do
alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.SyncTasks.SyncServerTask
alias DecentralisedBookIndex.Accounts.Role
@impl true
def render(assigns) do
~H"""
<.header>
Listing Servers
<:actions>
<div class="flex flex-row gap-2">
<%= if @current_user != nil and Role.can_administrate?(@current_user.role) do %>
<div class="flex flex-row gap-2">
<.primary_button phx-click="sync">
Sync now
</.primary_button>
</div>
<div class="flex flex-row gap-2">
<.link patch={~p"/oban"}>
<.primary_button>
Task dashboard
<div class="flex flex-row gap-2">
<.primary_button phx-click="sync">
Sync now
</.primary_button>
</.link>
</div>
<div class="flex flex-row gap-2">
<.link patch={~p"/oban"}>
<.primary_button>
Task dashboard
</.primary_button>
</.link>
</div>
</div>
</div>
<% end %>
</:actions>
</.header>
@@ -63,7 +67,9 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Index do
<.link navigate={~p"/servers/#{dbi_server}"}>Show</.link>
</div>
<.link patch={~p"/servers/#{dbi_server}/edit"}>Edit</.link>
<%= if @current_user != nil and Role.can_administrate?(@current_user.role) do %>
<.link patch={~p"/servers/#{dbi_server}/edit"}>Edit</.link>
<% end %>
</:action>
</.table>
@@ -1,6 +1,8 @@
defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
use DecentralisedBookIndexWeb, :live_view
alias DecentralisedBookIndex.Accounts.Role
@impl true
def render(assigns) do
~H"""
@@ -8,7 +10,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
{@dbi_server.name}
<:actions>
<%= if is_nil(@dbi_server.dbi_server) do %>
<%= if is_nil(@dbi_server.dbi_server) and @current_user != nil and Role.can_administrate?(@current_user.role) do %>
<.link patch={~p"/servers/#{@dbi_server}/edit"} phx-click={JS.push_focus()}>
<.edit_button>
Edit
@@ -18,7 +20,6 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
</:actions>
</.header>
<div>
<dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Url</dt>
@@ -28,6 +29,16 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Sync on?</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@dbi_server.sync_on?}</dd>
</dl>
<%= if not is_nil(@dbi_server.dbi_server) do %>
<dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">From Server</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
<.link navigate={~p"/servers/#{@dbi_server.dbi_server.id}/"} class="hover:underline">
{@dbi_server.dbi_server.name}
</.link>
</dd>
</dl>
<% end %>
</div>
<.back navigate={~p"/servers"}>Back to servers</.back>
@@ -1,6 +1,8 @@
defmodule DecentralisedBookIndexWeb.PublisherLive.Show do
use DecentralisedBookIndexWeb, :live_view
alias DecentralisedBookIndex.Accounts.Role
@impl true
def render(assigns) do
~H"""
@@ -18,6 +20,27 @@ defmodule DecentralisedBookIndexWeb.PublisherLive.Show do
</:actions>
</.header>
<%= if not is_nil(@publisher.dbi_server) do %>
<div>
<dl>
<dt class="mt-4 mb-2 font-semibold leading-none text-gray-900 dark:text-white">
From Server
</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) do %>
<.link navigate={~p"/servers/#{@publisher.dbi_server.id}/"} class="hover:underline">
{@publisher.dbi_server.name}
</.link>
<% else %>
<.link navigate={@publisher.dbi_server.url} class="hover:underline">
{@publisher.dbi_server.name}
</.link>
<% end %>
</dd>
</dl>
</div>
<% end %>
<.back navigate={~p"/publishers"}>Back to publishers</.back>
"""
end
@@ -13,7 +13,7 @@ defmodule DecentralisedBookIndexWeb.Plugs.RequireAdmin do
conn
else
conn
|> put_flash(:error, "Unauthorized")
|> put_flash(:error, "Unauthorized!")
|> redirect(to: "/")
|> halt()
end
+12 -10
View File
@@ -41,6 +41,18 @@ defmodule DecentralisedBookIndexWeb.Router do
scope "/", DecentralisedBookIndexWeb do
pipe_through :browser
ash_authentication_live_session :admin_authenticated_routes,
on_mount: {DecentralisedBookIndexWeb.LiveUserAuth, :admin_required} do
live "/servers/new", DbiServerLive.Edit, :new
live "/servers/:id/edit", DbiServerLive.Edit, :edit
#live "/servers", DbiServerLive.Index, :index
#live "/servers/:id", DbiServerLive.Show, :show
live "/users", UserLive.Index, :index
live "/users/:id", UserLive.Show, :show
live "/users/:id/edit", UserLive.Edit, :edit
end
ash_authentication_live_session :moderator_authenticated_routes,
on_mount: {DecentralisedBookIndexWeb.LiveUserAuth, :moderator_required} do
live "/books/new", BookLive.Edit, :new
@@ -56,19 +68,9 @@ defmodule DecentralisedBookIndexWeb.Router do
live "/publishers/:id/edit", PublisherLive.Edit, :edit
live "/publishers/:id", PublisherLive.Show, :show
end
ash_authentication_live_session :admin_authenticated_routes,
on_mount: {DecentralisedBookIndexWeb.LiveUserAuth, :admin_required} do
live "/servers", DbiServerLive.Index, :index
live "/servers/new", DbiServerLive.Edit, :new
live "/servers/:id/edit", DbiServerLive.Edit, :edit
live "/servers/:id", DbiServerLive.Show, :show
live "/users", UserLive.Index, :index
live "/users/:id", UserLive.Show, :show
live "/users/:id/edit", UserLive.Edit, :edit
end
ash_authentication_live_session :maybe_authenticated_routes,
+1 -1
View File
@@ -4,7 +4,7 @@ defmodule DecentralisedBookIndex.MixProject do
def project do
[
app: :decentralised_book_index,
version: "0.1.0",
version: "1.0.0",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
@@ -56,7 +56,7 @@ defmodule DecentralisedBookIndex.Metadata.AuthorTest do
test "new author has no alternatives names", %{user: user} do
{:ok, author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
assert {:ok, alternatives_names} = Metadata.get_author_alternative_names(author)
assert alternatives_names = []
assert ^alternatives_names = []
end
test "author has related author so they has one alternative name", %{user: user} do
@@ -73,7 +73,7 @@ defmodule DecentralisedBookIndex.Metadata.AuthorTest do
)
assert {:ok, alternatives_names} = Metadata.get_author_alternative_names(author)
assert alternatives_names = [related_author]
assert get_ids(alternatives_names) == [related_author.id]
end
end
@@ -118,7 +118,8 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
bids,
author_roles,
publisher.id,
nil
nil,
actor: user
)
end
end
@@ -146,7 +147,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
)
assert {:ok, alternatives_names} = Metadata.get_book_alternative_editions(book)
assert alternatives_names = []
assert ^alternatives_names = []
end
test "book has related book so they has one alternative name", %{user: user} do
@@ -187,7 +188,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
)
assert {:ok, alternatives_names} = Metadata.get_book_alternative_editions(book)
assert alternatives_names = [related_book]
assert get_ids(alternatives_names) == [related_book.id]
end
end
@@ -201,7 +202,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
%{order: 1, author_id: author.id, role: ""}
]
{:ok, book} =
{:ok, _book} =
Metadata.create_book(
"Book",
"An description",
@@ -217,7 +218,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
actor: user
)
assert {:ok, books} = Metadata.get_author_books(author)
assert {:ok, _books} = Metadata.get_author_books(author)
end
test "get the list contains aliases' books", %{user: user} do
@@ -256,7 +257,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
end
describe "get a book by book id" do
test "get by bid", %{user: user} do
test "get by bid" do
expected_book = generate(book())
[%Metadata.BookId{type: type, bid: bid} | _] = expected_book.bids
@@ -12,13 +12,8 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
test "user can't submit" do
user = generate(user())
valid_params = %{
"name" => "Oleh",
"description" => "A cool author"
}
assert_raise Ash.Error.Forbidden, fn ->
form =
_form =
AshPhoenix.Form.for_create(Metadata.Author, :create,
as: "author",
actor: user
@@ -28,12 +23,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
end
test "moderator can submit", %{user: user} do
valid_params = %{
"name" => "Oleh",
"description" => "A cool author"
}
assert form =
assert _form =
AshPhoenix.Form.for_create(Metadata.Author, :create,
as: "author",
actor: user
@@ -55,7 +45,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
actor: user
)
assert {:ok, author} = AshPhoenix.Form.submit(form, params: valid_params)
assert {:ok, _author} = AshPhoenix.Form.submit(form, params: valid_params)
end
end
@@ -74,7 +64,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
actor: user
)
assert {:ok, author} = AshPhoenix.Form.submit(form, params: valid_params)
assert {:ok, _author} = AshPhoenix.Form.submit(form, params: valid_params)
end
end
end
@@ -4,7 +4,6 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
import ExUnitProperties
alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.Metadata.Book
setup do
user = generate(user(role: :moderator))
@@ -14,22 +13,9 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
describe "form submiting" do
test "user can't submit" do
user = generate(user())
bids = bids()
author_roles = author_roles()
valid_params = %{
"title" => "Book",
"description" => "A cool author",
"format" => "Paper",
"language" => "English",
"page_count" => 600,
"published" => ~D[2025-03-06],
"author_roles" => author_roles,
"bids" => bids
}
assert_raise Ash.Error.Forbidden, fn ->
form =
_form =
AshPhoenix.Form.for_create(Metadata.Book, :create,
as: "book",
actor: user
@@ -39,21 +25,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
end
test "moderator can submit", %{user: user} do
bids = bids(actor: user)
author_roles = author_roles(actor: user)
valid_params = %{
"title" => "Book",
"description" => "A cool author",
"format" => "Paper",
"language" => "English",
"page_count" => 600,
"published" => ~D[2025-03-06],
"author_roles" => author_roles,
"bids" => bids
}
assert form =
assert _form =
AshPhoenix.Form.for_create(Metadata.Book, :create,
as: "book",
actor: user
@@ -86,7 +58,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
actor: user
)
assert {:ok, user} = AshPhoenix.Form.submit(form, params: valid_params)
assert {:ok, _book} = AshPhoenix.Form.submit(form, params: valid_params)
end
end
@@ -106,7 +78,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
actor: user
)
assert {:ok, user} = AshPhoenix.Form.submit(form, params: valid_params)
assert {:ok, _book} = AshPhoenix.Form.submit(form, params: valid_params)
end
test "update bids and authors", %{user: user} do
@@ -125,7 +97,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
actor: user
)
assert {:ok, user} = AshPhoenix.Form.submit(form, params: valid_params)
assert {:ok, _book} = AshPhoenix.Form.submit(form, params: valid_params)
end
end
@@ -12,12 +12,8 @@ defmodule DecentralisedBookIndex.Metadata.Forms.PublisherFormTest do
test "user can't submit" do
user = generate(user())
valid_params = %{
"name" => "Publisher"
}
assert_raise Ash.Error.Forbidden, fn ->
form =
_form =
AshPhoenix.Form.for_create(Metadata.Publisher, :create,
as: "publisher",
actor: user
@@ -27,11 +23,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.PublisherFormTest do
end
test "moderator can submit", %{user: user} do
valid_params = %{
"name" => "Publisher"
}
assert form =
assert _form =
AshPhoenix.Form.for_create(Metadata.Publisher, :create,
as: "publisher",
actor: user
@@ -81,5 +81,40 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorTransformerTest do
updated_at: "2025-03-26T10:16:16.124581Z"
} = author
end
test "a json has dbi_server_id as nil, then author's map has not :dbi_server_id key" do
json_body = %{
"attributes" => %{
"author_alias_registry_id" => "1320a6fe-b311-45ac-bbaa-39cf29c44624",
"avatar_url" => "/images/avatar.png",
"dbi_server_id" => nil,
"description" => "Something",
"inserted_at" => "2025-03-25T19:19:15.187456Z",
"name" => "Author",
"updated_at" => "2025-03-26T10:16:16.124581Z"
},
"id" => "889a323e-d104-4b5d-b276-dad5a9b1da9d",
"links" => %{},
"meta" => %{},
"relationships" => %{
"author_alias_registry" => %{"links" => %{}, "meta" => %{}},
"dbi_server" => %{"links" => %{}, "meta" => %{}}
},
"type" => "author"
}
assert {:ok, author} = AuthorTransformer.from_json(json_body)
assert %{
id: "889a323e-d104-4b5d-b276-dad5a9b1da9d",
name: "Author",
description: "Something",
avatar_url: "/images/avatar.png",
author_alias_registry_id: "1320a6fe-b311-45ac-bbaa-39cf29c44624",
inserted_at: "2025-03-25T19:19:15.187456Z",
updated_at: "2025-03-26T10:16:16.124581Z"
} = author
refute Map.has_key?(author, :dbi_server_id)
end
end
end
@@ -138,5 +138,70 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookTransformerTest do
dbi_server_id: "889a323e-d104-4b5d-b276-dad5a9b1da99"
} = book
end
test "a json has dbi_server_id as nil, then book's map has not :dbi_server_id key" do
json_body = %{
"attributes" => %{
"dbi_server_id" => nil,
"cover_image_url" => "/images/book_cover.png",
"description" => "A cool book.",
"format" => "Paper",
"inserted_at" => "2025-03-20T14:44:36.162986Z",
"language" => "English",
"page_count" => 1000,
"published" => "2025-03-05",
"publisher_id" => "11349865-1b7b-454a-b999-6c4059888a78",
"title" => "Book",
"updated_at" => "2025-04-01T18:14:25.754055Z"
},
"id" => "1bbe8861-9d9d-4684-bda6-b6ec238d8d08",
"links" => %{},
"meta" => %{},
"relationships" => %{
"author_roles" => %{
"links" => %{
"related" =>
"http://localhost:4000/api/v1/json/books/1bbe8861-9d9d-4684-bda6-b6ec238d8d08/author_roles"
},
"meta" => %{}
},
"bids" => %{
"links" => %{
"related" =>
"http://localhost:4000/api/v1/json/books/1bbe8861-9d9d-4684-bda6-b6ec238d8d08/bids"
},
"meta" => %{}
},
"book_editions_registry" => %{"links" => %{}, "meta" => %{}},
"dbi_server" => %{"links" => %{}, "meta" => %{}},
"publisher" => %{
"links" => %{
"related" =>
"http://localhost:4000/api/v1/json/books/1bbe8861-9d9d-4684-bda6-b6ec238d8d08/publisher"
},
"meta" => %{}
}
},
"type" => "book"
}
assert {:ok, book} = BookTransformer.from_json(json_body)
assert %{
id: "1bbe8861-9d9d-4684-bda6-b6ec238d8d08",
cover_image_url: "/images/book_cover.png",
description: "A cool book.",
format: "Paper",
inserted_at: "2025-03-20T14:44:36.162986Z",
language: "English",
page_count: 1000,
published: "2025-03-05",
publisher_id: "11349865-1b7b-454a-b999-6c4059888a78",
title: "Book",
updated_at: "2025-04-01T18:14:25.754055Z",
} = book
refute Map.has_key?(book, :dbi_server_id)
end
end
end
@@ -8,6 +8,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
json_body = %{
"data" => %{
"attributes" => %{
"dbi_server_id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
"inserted_at" => "2025-03-22T20:07:30.766249Z",
"name" => "Test",
"updated_at" => "2025-05-04T18:48:44.213309Z",
@@ -16,7 +17,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
"id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
"links" => %{},
"meta" => %{},
"relationships" => %{},
"relationships" => %{"dbi_server" => %{"links" => %{}, "meta" => %{}}},
"type" => "dbi_server"
},
"jsonapi" => %{"version" => "1.0"},
@@ -34,11 +35,40 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
name: "Test",
url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z"
updated_at: "2025-05-04T18:48:44.213309Z",
dbi_server_id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc"
} = server
end
test "a json doesn't contains server information \"data\" attribute" do
json_body = %{
"attributes" => %{
"dbi_server_id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
"inserted_at" => "2025-03-22T20:07:30.766249Z",
"name" => "Test",
"updated_at" => "2025-05-04T18:48:44.213309Z",
"url" => "http://localhost:4001"
},
"id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
"links" => %{},
"meta" => %{},
"relationships" => %{"dbi_server" => %{"links" => %{}, "meta" => %{}}},
"type" => "dbi_server"
}
assert {:ok, server} = DBIServerTransformer.from_json(json_body)
assert %{
id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
name: "Test",
url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z",
dbi_server_id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc"
} = server
end
test "a json has dbi_server_id as nil, then server's map has not :dbi_server_id key" do
json_body = %{
"attributes" => %{
"inserted_at" => "2025-03-22T20:07:30.766249Z",
@@ -62,6 +92,8 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z"
} = server
refute Map.has_key?(server, :dbi_server_id)
end
end
end
@@ -63,5 +63,32 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.PublisherTransformerTest
dbi_server_id: "889a323e-d104-4b5d-b276-dad5a9b1da99"
} = publisher
end
test "a json has dbi_server_id as nil, then publisher's map has not :dbi_server_id key" do
json_body = %{
"attributes" => %{
"dbi_server_id" => nil,
"inserted_at" => "2025-03-21T09:20:48.791539Z",
"name" => "Publisher",
"updated_at" => "2025-03-21T09:20:48.791539Z"
},
"id" => "11349865-1b7b-454a-b999-6c4059888a78",
"links" => %{},
"meta" => %{},
"relationships" => %{"dbi_server" => %{"links" => %{}, "meta" => %{}}},
"type" => "publisher"
}
assert {:ok, publisher} = PublisherTransformer.from_json(json_body)
assert %{
id: "11349865-1b7b-454a-b999-6c4059888a78",
name: "Publisher",
inserted_at: "2025-03-21T09:20:48.791539Z",
updated_at: "2025-03-21T09:20:48.791539Z",
} = publisher
refute Map.has_key?(publisher, :dbi_server_id)
end
end
end
@@ -26,17 +26,13 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorSyncTest do
updated_at: "2025-03-21T09:20:48.791539Z"
}
{:ok, inserted_at, 0} = DateTime.from_iso8601(author[:inserted_at])
{:ok, updated_at, 0} = DateTime.from_iso8601(author[:updated_at])
assert :ok = AuthorSync.create_update(author, server.id)
assert {:ok, saved_author} = Metadata.get_author_by_id(author.id)
author =
author
|> Map.replace(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_at)
|> Map.delete(:author_alias_registry_id)
|> datetime_from_iso8601_for_map()
assert get_submap(saved_author, author) == author
assert nil != saved_author.author_alias_registry_id
@@ -56,16 +52,12 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorSyncTest do
updated_at: "2025-03-21T09:20:48.791539Z"
}
{:ok, inserted_at, 0} = DateTime.from_iso8601(author[:inserted_at])
{:ok, updated_at, 0} = DateTime.from_iso8601(author[:updated_at])
assert :ok = AuthorSync.create_update(author, server.id)
assert {:ok, saved_author} = Metadata.get_author_by_id(author.id)
author =
author
|> Map.replace(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_at)
|> datetime_from_iso8601_for_map()
assert get_submap(saved_author, author) == author
assert nil != saved_author.author_alias_registry_id
@@ -88,7 +80,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorSyncTest do
assert :ok = AuthorSync.create_update(author_attrs, server.id)
assert {:ok, saved_author} = Metadata.get_author_by_id(author.id)
assert author = saved_author
assert get_submap(author, author_attrs) == get_submap(saved_author, author_attrs)
end
test "the local existing author wouldn't updated", %{user: user} do
@@ -32,20 +32,17 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookSyncTest do
updated_at: "2025-04-01T18:14:25.754055Z"
}
{:ok, inserted_at, 0} = DateTime.from_iso8601(book[:inserted_at])
{:ok, updated_at, 0} = DateTime.from_iso8601(book[:updated_at])
assert :ok = BookSync.create_update(book, server.id)
assert {:ok, saved_book} = Metadata.get_book_by_id(book.id)
book =
book
|> Map.replace(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_at)
|> datetime_from_iso8601_for_map()
|> Map.replace(:published, Date.from_iso8601!(book[:published]))
assert nil != saved_book.book_editions_registry_id
assert server.id == saved_book.dbi_server_id
assert get_submap(saved_book, book) == book
end
test "a new book has the same editions registry" do
@@ -68,20 +65,17 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookSyncTest do
book_editions_registry_id: "1ed81d9e-3757-472a-aa3b-44fcdfbe770a"
}
{:ok, inserted_at, 0} = DateTime.from_iso8601(book[:inserted_at])
{:ok, updated_at, 0} = DateTime.from_iso8601(book[:updated_at])
assert :ok = BookSync.create_update(book, server.id)
assert {:ok, saved_book} = Metadata.get_book_by_id(book.id)
book =
book
|> Map.replace(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_at)
|> datetime_from_iso8601_for_map()
|> Map.replace(:published, Date.from_iso8601!(book[:published]))
assert nil != saved_book.book_editions_registry_id
assert server.id == saved_book.dbi_server_id
assert get_submap(saved_book, book) == book
end
test "a new book with relationships will be created" do
@@ -166,10 +160,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookSyncTest do
}
assert :ok = BookSync.create_update(book_attrs, server.id)
assert {:ok, saved_book} = Metadata.get_book_by_id(book.id)
assert {:ok, _saved_book} = Metadata.get_book_by_id(book.id)
end
test "the local existing book wouldn't updated", %{user: user} do
test "the local existing book wouldn't updated" do
server = generate(dbi_server(url: @test_server_endpoint))
book = generate(book())
@@ -195,7 +189,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookSyncTest do
assert saved_book.dbi_server_id == nil
end
test "old book wouldn't be sync", %{user: user} do
test "old book wouldn't be sync" do
server = generate(dbi_server(url: @test_server_endpoint))
book = generate(book())
@@ -347,11 +341,4 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookSyncTest do
assert Enum.all?(old_roles, fn id -> id not in new_roles end)
end
end
defp get_ids(records) do
Enum.map(records, fn record -> get_id(record) end)
end
defp get_id(record) when is_struct(record), do: record.id
defp get_id(record) when is_map(record), do: record[:id]
end
@@ -17,11 +17,11 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerSyncTest do
server = generate(dbi_server(url: @test_server_endpoint))
dbi_server = %{
id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
name: "Test",
url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z"
id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
name: "Test",
url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z"
}
{:ok, inserted_at, 0} = DateTime.from_iso8601(dbi_server[:inserted_at])
@@ -35,27 +35,30 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerSyncTest do
|> Map.replace(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_at)
assert dbi_server = saved_dbi_server
assert dbi_server == get_submap(saved_dbi_server, dbi_server)
assert server.id == saved_dbi_server.dbi_server_id
end
test "update an existing dbi_server", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint))
{:ok, dbi_server} = Metadata.create_dbi_server("Test", "http://localhost:4001", false, actor: user)
{:ok, dbi_server} =
Metadata.create_dbi_server("Test", "http://localhost:4001", false, actor: user)
dbi_server_attrs = %{
id: dbi_server.id,
name: "Test",
url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z"
id: dbi_server.id,
name: "Test",
url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z"
}
assert :ok = DBIServerSync.create_update(dbi_server_attrs, server.id)
assert {:ok, saved_dbi_server} = Metadata.get_dbi_server_by_id(dbi_server.id)
assert dbi_server_attrs = saved_dbi_server
assert datetime_from_iso8601_for_map(dbi_server_attrs) ==
get_submap(saved_dbi_server, dbi_server_attrs)
assert server.id == saved_dbi_server.dbi_server_id
end
end
@@ -23,9 +23,6 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.PublisherSyncTest do
updated_at: "2025-03-21T09:20:48.791539Z"
}
{:ok, inserted_at, 0} = DateTime.from_iso8601(publisher[:inserted_at])
{:ok, updated_at, 0} = DateTime.from_iso8601(publisher[:updated_at])
assert :ok = PublisherSync.create_update(publisher, server.id)
assert {:ok, saved_publisher} = Metadata.get_publisher_by_id(publisher.id)
@@ -16,10 +16,10 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncAuthorsTaskTest do
test "sync authors", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint))
{:ok, author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
{:ok, author} = Metadata.create_author("Author2", "An description", nil, nil, actor: user)
{:ok, _author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
{:ok, _author} = Metadata.create_author("Author2", "An description", nil, nil, actor: user)
assert server = SyncAuthorsTask.sync(server)
assert ^server = SyncAuthorsTask.sync(server)
end
end
end
@@ -2,7 +2,6 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncBookTaskTest do
use DecentralisedBookIndex.DataCase
alias DecentralisedBookIndex.SyncTasks.SyncBooksTask
alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.TestEndpoints
@test_server_endpoint TestEndpoints.test_api_endpoint()
@@ -14,7 +13,7 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncBookTaskTest do
_book = generate(book())
_book = generate(book())
assert server = SyncBooksTask.sync(server)
assert ^server = SyncBooksTask.sync(server)
end
end
end
@@ -19,7 +19,7 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncDBIServerTaskTest do
{:ok, _dbi_server} = Metadata.create_dbi_server("Test", "http://localhost:4001", false, actor: user)
{:ok, _dbi_server} = Metadata.create_dbi_server("Test2", "http://localhost:4001", false, actor: user)
assert server = SyncDBIServerTask.sync(server)
assert ^server = SyncDBIServerTask.sync(server)
end
end
end
@@ -19,7 +19,7 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncPublishersTaskTest do
{:ok, _publisher} = Metadata.create_publisher("Publisher", actor: user)
{:ok, _publisher} = Metadata.create_publisher("Publisher2", actor: user)
assert server = SyncPublishersTask.sync(server)
assert ^server = SyncPublishersTask.sync(server)
end
end
end
@@ -3,9 +3,6 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncServerTaskTest do
alias DecentralisedBookIndex.SyncTasks.SyncServerTask
alias DecentralisedBookIndex.Sync.ApiClients.FetchJson
alias DecentralisedBookIndex.Sync.AuthorSync
alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.TestEndpoints
@@ -18,19 +15,19 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncServerTaskTest do
describe "sync all" do
test "servers", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint))
{:ok, author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
_server = generate(dbi_server(url: @test_server_endpoint))
{:ok, _author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
assert server = SyncServerTask.sync_all()
assert :ok = SyncServerTask.sync_all()
end
end
describe "sync one" do
test "server", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint))
{:ok, author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
{:ok, _author} = Metadata.create_author("Author", "An description", nil, nil, actor: user)
assert server = SyncServerTask.sync_one(server)
assert :ok = SyncServerTask.sync_one(server)
end
end
end
@@ -0,0 +1,118 @@
defmodule DecentralisedBookIndexWeb.LiveViewsPermissionsForOtherServersDataTest do
use DecentralisedBookIndexWeb.LiveCase, async: true
alias DecentralisedBookIndex.TestEndpoints
@test_server_endpoint TestEndpoints.test_api_endpoint()
setup do
moderator = generate(user(role: :moderator))
admin = generate(user(role: :admin))
server = generate(dbi_server(url: @test_server_endpoint))
%{
server: server,
moderator: moderator,
admin: admin,
users: [moderator, admin]
}
end
describe "Book Edit /books/:id/edit" do
setup %{server: server} do
%{book: generate(book(dbi_server_id: server.id))}
end
test "can't edit other servers' data", %{conn: conn, users: users, book: book} do
for user <- users do
redirect_url = "/books/#{book.id}"
assert {:error,
{:redirect,
%{flash: %{"error" => "Can't edit other server's data!"}, to: ^redirect_url}}} =
conn
|> log_in_user(user)
|> live("/books/#{book.id}/edit")
end
end
end
describe "Author Edit /authors/:id/edit" do
setup %{server: server} do
%{author: generate(author(dbi_server_id: server.id))}
end
test "can't edit other servers' data", %{conn: conn, users: users, author: author} do
for user <- users do
redirect_url = "/authors/#{author.id}"
assert {:error,
{:redirect,
%{flash: %{"error" => "Can't edit other server's data!"}, to: ^redirect_url}}} =
conn
|> log_in_user(user)
|> live("/authors/#{author.id}/edit")
end
end
end
describe "Publisher Edit /publishers/:id/edit" do
setup %{server: server} do
%{publisher: generate(publisher(dbi_server_id: server.id))}
end
test "can't edit other servers' data", %{conn: conn, admin: admin, publisher: publisher} do
for user <- [admin] do
redirect_url = "/publishers/#{publisher.id}"
assert {:error,
{:redirect,
%{flash: %{"error" => "Can't edit other server's data!"}, to: ^redirect_url}}} =
conn
|> log_in_user(user)
|> live("/publishers/#{publisher.id}/edit")
end
end
end
describe "Server Edit /servers/:id/edit" do
setup %{server: server} do
%{dbi_server: generate(dbi_server(dbi_server_id: server.id))}
end
test "can edit other servers' data", %{conn: conn, admin: admin, dbi_server: dbi_server} do
for user <- [admin] do
assert {:ok, _view, _html} =
conn
|> log_in_user(user)
|> live("/servers/#{dbi_server.id}/edit")
end
end
test "can edit this server data", %{conn: conn, admin: admin} do
dbi_server = generate(dbi_server())
for user <- [admin] do
assert {:ok, view, _html} =
conn
|> log_in_user(user)
|> live("/servers/#{dbi_server.id}/edit")
assert has_element?(view, "input#dbi_server_name")
assert has_element?(view, "input#dbi_server_url")
end
end
test "can edit other servers' data, but only sync_on?", %{conn: conn, admin: admin, dbi_server: dbi_server} do
for user <- [admin] do
assert {:ok, view, _html} =
conn
|> log_in_user(user)
|> live("/servers/#{dbi_server.id}/edit")
refute has_element?(view, "input#dbi_server_name")
refute has_element?(view, "input#dbi_server_url")
end
end
end
end
@@ -0,0 +1,514 @@
defmodule DecentralisedBookIndexWeb.LiveViewsPermissionsTest do
use DecentralisedBookIndexWeb.LiveCase, async: true
setup do
user = generate(user(role: :user))
moderator = generate(user(role: :moderator))
admin = generate(user(role: :admin))
%{
user: user,
moderator: moderator,
admin: admin,
users: [nil, user, moderator, admin]
}
end
describe "Book Index /books" do
test "can be accessed by any user", %{conn: conn, users: users} do
for user <- users do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/books")
assert html =~ "Listing Books"
end
end
end
describe "Book Show /books/:id" do
setup do
%{book: generate(book())}
end
test "can be accessed by any user", %{conn: conn, users: users, book: book} do
for user <- users do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/books/#{book.id}")
assert html =~ book.title
end
end
end
describe "Book Edit /books/:id/new" do
test "can't be accessed by regular user", %{conn: conn, user: user} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/books/new")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/books/new")
assert html =~ "New Book"
end
end
end
describe "Book Edit /books/:id/edit" do
setup do
%{book: generate(book())}
end
test "can't be accessed by regular user", %{conn: conn, user: user, book: book} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/books/#{book.id}/edit")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin,
book: book
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/books/#{book.id}/edit")
assert html =~ "Edit Book"
end
end
end
describe "Author Index /authors" do
test "can be accessed by any user", %{conn: conn, users: users} do
for user <- users do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/authors")
assert html =~ "Listing Authors"
end
end
end
describe "Author Show /authors/:id" do
setup do
%{author: generate(author())}
end
test "can be accessed by any user", %{conn: conn, users: users, author: author} do
for user <- users do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/authors/#{author.id}")
assert html =~ author.name
end
end
end
describe "Author Edit /authors/:id/new" do
test "can't be accessed by regular user", %{conn: conn, user: user} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/authors/new")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/authors/new")
assert html =~ "New Author"
end
end
end
describe "Author Edit /authors/:id/edit" do
setup do
%{author: generate(author())}
end
test "can't be accessed by regular user", %{conn: conn, user: user, author: author} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/authors/#{author.id}/edit")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin,
author: author
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/authors/#{author.id}/edit")
assert html =~ "Edit Author"
end
end
end
describe "Publisher Index /publishers" do
test "can't be accessed by regular user", %{conn: conn, user: user} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/publishers")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin
} do
for user <- [moderator, admin] do
assert {:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/publishers")
assert html =~ "Listing Publishers"
end
end
end
describe "Publisher Show /publishers/:id" do
setup do
%{publisher: generate(publisher())}
end
test "can't be accessed by regular user", %{conn: conn, user: user, publisher: publisher} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/publishers/#{publisher.id}")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin,
publisher: publisher
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/publishers/#{publisher.id}")
assert html =~ publisher.name
end
end
end
describe "Publisher Edit /publishers/:id/new" do
test "can't be accessed by regular user", %{conn: conn, user: user} do
for user <- [nil, user] do
{:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/publishers/new")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/publishers/new")
assert html =~ "New Publisher"
end
end
end
describe "Publisher Edit /publishers/:id/edit" do
setup do
%{publisher: generate(publisher())}
end
test "can't be accessed by regular user", %{conn: conn, user: user, publisher: publisher} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/publishers/#{publisher.id}/edit")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin,
publisher: publisher
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/publishers/#{publisher.id}/edit")
assert html =~ "Edit Publisher"
end
end
end
describe "Server Index /servers" do
test "can't be accessed by regular user", %{
conn: conn,
user: user
} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/servers")
end
end
test "can be accessed by moderator and admin", %{
conn: conn,
moderator: moderator,
admin: admin
} do
for user <- [moderator, admin] do
assert {:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/servers")
assert html =~ "Listing Servers"
end
end
end
describe "Server Show /servers/:id" do
setup do
%{server: generate(dbi_server())}
end
test "can't be accessed by non-moderator user", %{
conn: conn,
user: user,
server: server
} do
for user <- [nil, user] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/servers/#{server.id}")
end
end
test "can be accessed by admin", %{
conn: conn,
moderator: moderator,
admin: admin,
server: server
} do
for user <- [moderator, admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/servers/#{server.id}")
assert html =~ server.name
end
end
end
describe "Server Edit /servers/:id/new" do
test "can't be accessed by non-admin user", %{conn: conn, user: user, moderator: moderator} do
for user <- [nil, user, moderator] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/servers/new")
end
end
test "can be accessed by admin", %{conn: conn, admin: admin} do
for user <- [admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/servers/new")
assert html =~ "New Server"
end
end
end
describe "Server Edit /servers/:id/edit" do
setup do
%{server: generate(dbi_server())}
end
test "can't be accessed by non-admin user", %{
conn: conn,
user: user,
moderator: moderator,
server: server
} do
for user <- [nil, user, moderator] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/servers/#{server.id}/edit")
end
end
test "can be accessed by admin", %{conn: conn, admin: admin, server: server} do
for user <- [admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/servers/#{server.id}/edit")
assert html =~ "Edit Server"
end
end
end
describe "ObanWeb /oban" do
test "can't be accessed by regular user and moderator", %{
conn: conn,
user: user,
moderator: moderator
} do
for user <- [nil, user, moderator] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/oban")
end
end
end
describe "User Index /users" do
test "can't be accessed by regular user and moderator", %{
conn: conn,
user: user,
moderator: moderator
} do
for user <- [nil, user, moderator] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/users")
end
end
test "can be accessed by admin", %{conn: conn, admin: admin} do
for user <- [admin] do
assert {:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/users")
assert html =~ "Listing Users"
end
end
end
describe "User Show /users/:id" do
test "can't be accessed by non-admin user", %{
conn: conn,
user: regular_user,
moderator: moderator
} do
for user <- [nil, regular_user, moderator] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/users/#{regular_user.id}")
end
end
test "can be accessed by admin", %{conn: conn, admin: admin} do
for user <- [admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/users/#{user.id}")
assert html =~ user.email.string
end
end
end
describe "User Edit /users/:id/edit" do
test "can't be accessed by non-admin user", %{
conn: conn,
user: regular_user,
moderator: moderator
} do
for user <- [nil, regular_user, moderator] do
assert {:error, {:redirect, %{flash: %{"error" => "Unauthorized!"}, to: "/"}}} =
conn
|> log_in_user(user)
|> live("/users/#{regular_user.id}/edit")
end
end
test "can be accessed by admin", %{conn: conn, admin: admin} do
for user <- [admin] do
{:ok, _view, html} =
conn
|> log_in_user(user)
|> live("/users/#{user.id}/edit")
assert html =~ "Edit User"
end
end
end
end
+13 -6
View File
@@ -35,13 +35,16 @@ defmodule DecentralisedBookIndex.Generators do
generate(user(role: :moderator))
end)
dbi_server_id = opts[:dbi_server_id] || nil
changeset_generator(
Metadata.Author,
:create,
defaults: [
name: sequence(:name, &"Author #{&1}"),
description: sequence(:name, &"Description #{&1}"),
author_alias_registry_id: nil
author_alias_registry_id: nil,
dbi_server_id: dbi_server_id
],
overrides: opts,
actor: actor
@@ -83,16 +86,17 @@ defmodule DecentralisedBookIndex.Generators do
url =
opts[:url] ||
once(:default_actor, fn ->
"http://localhost:4002"
end)
"http://localhost:4002"
dbi_server_id = opts[:dbi_server_id] || nil
changeset_generator(
Metadata.DBIServer,
:create,
defaults: [
name: sequence(:name, &"Server #{&1}"),
url: url
url: url,
dbi_server_id: dbi_server_id
],
overrides: opts,
actor: actor
@@ -136,11 +140,14 @@ defmodule DecentralisedBookIndex.Generators do
generate(user(role: :moderator))
end)
dbi_server_id = opts[:dbi_server_id] || nil
changeset_generator(
Metadata.Publisher,
:create,
defaults: [
name: sequence(:name, &"Publisher #{&1}")
name: sequence(:name, &"Publisher #{&1}"),
dbi_server_id: dbi_server_id
],
overrides: opts,
actor: actor
+18
View File
@@ -40,4 +40,22 @@ defmodule DecentralisedBookIndex.Helpers do
|> DateTime.add(seconds)
|> DateTime.to_iso8601()
end
def get_ids(records) do
Enum.map(records, fn record -> get_id(record) end)
end
def get_id(record) when is_struct(record), do: record.id
def get_id(record) when is_map(record), do: record[:id]
@doc """
Logs the given `user` into the `conn`.
It returns an updated `conn`.
"""
def log_in_user(conn, user) do
conn
|> Phoenix.ConnTest.init_test_session(%{})
|> AshAuthentication.Plug.Helpers.store_in_session(user)
end
end
+75
View File
@@ -0,0 +1,75 @@
defmodule DecentralisedBookIndexWeb.LiveCase do
@moduledoc """
This module defines the setup for tests requiring
access to the application's data layer.
You may define functions here to be used as helpers in
your tests.
Finally, if the test case interacts with the database,
we enable the SQL sandbox, so changes done to the database
are reverted at the end of every test. If you are using
PostgreSQL, you can even run database tests asynchronously
by setting `use DecentralisedBookIndex.DataCase, async: true`, although
this option is not recommended for other databases.
"""
use ExUnit.CaseTemplate
using do
quote do
alias DecentralisedBookIndex.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import DecentralisedBookIndex.DataCase
import DecentralisedBookIndex.Generators
import DecentralisedBookIndex.Helpers
# The default endpoint for testing
@endpoint DecentralisedBookIndexWeb.Endpoint
use DecentralisedBookIndexWeb, :verified_routes
# Import conveniences for testing with connections
import Plug.Conn
import Phoenix.ConnTest
import DecentralisedBookIndexWeb.ConnCase
import Phoenix.LiveViewTest
end
end
setup tags do
DecentralisedBookIndex.DataCase.setup_sandbox(tags)
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
@doc """
Sets up the sandbox based on the test tags.
"""
def setup_sandbox(tags) do
pid =
Ecto.Adapters.SQL.Sandbox.start_owner!(DecentralisedBookIndex.Repo,
shared: not tags[:async]
)
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
end
@doc """
A helper that transforms changeset errors into a map of messages.
assert {:error, changeset} = Accounts.create_user(%{password: "short"})
assert "password is too short" in errors_on(changeset).password
assert %{password: ["password is too short"]} = errors_on(changeset)
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
end