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: commands:
- mix test - 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: volumes:
- name: mix - name: mix
temp: {} temp: {}
+2 -1
View File
@@ -12,7 +12,8 @@ config :decentralised_book_index, Oban,
repo: DecentralisedBookIndex.Repo, repo: DecentralisedBookIndex.Repo,
queues: [default: 14], queues: [default: 14],
plugins: [ plugins: [
Oban.Plugins.Pruner, # A job will be pruned after 7 days.
{Oban.Plugins.Pruner, max_age: 7*24*60*60},
{Oban.Plugins.Cron, {Oban.Plugins.Cron,
crontab: [ crontab: [
{"@daily", DecentralisedBookIndex.SyncWorker}, {"@daily", DecentralisedBookIndex.SyncWorker},
+4 -4
View File
@@ -6,7 +6,7 @@ defmodule DecentralisedBookIndex.Metadata do
json_api do json_api do
routes do routes do
base_route "/books", Metadata.Book do base_route "/books", Metadata.Book do
get :read get :by_id
index :search index :search
related :bids, :read, primary?: true related :bids, :read, primary?: true
@@ -15,17 +15,17 @@ defmodule DecentralisedBookIndex.Metadata do
end end
base_route "/authors", Metadata.Author do base_route "/authors", Metadata.Author do
get :read get :by_id
index :search index :search
end end
base_route "/publishers", Metadata.Publisher do base_route "/publishers", Metadata.Publisher do
get :read get :by_id
index :search index :search
end end
base_route "/servers", Metadata.DBIServer do base_route "/servers", Metadata.DBIServer do
get :read get :by_id
index :search index :search
end end
end end
@@ -25,12 +25,16 @@ defmodule DecentralisedBookIndex.Metadata.Author do
end end
end end
resource do
description "An author's metadata."
end
actions do actions do
defaults [:read, :destroy] defaults [:read, :destroy]
create :create do create :create do
primary? true 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 -> change fn changeset, context ->
actor = Map.get(context, :actor, nil) actor = Map.get(context, :actor, nil)
@@ -100,6 +104,7 @@ defmodule DecentralisedBookIndex.Metadata.Author do
end end
read :by_id do read :by_id do
description "Return the Author by its id."
argument :id, :uuid, allow_nil?: false argument :id, :uuid, allow_nil?: false
get? true get? true
filter expr(id == ^arg(:id)) filter expr(id == ^arg(:id))
@@ -136,7 +141,10 @@ defmodule DecentralisedBookIndex.Metadata.Author do
end end
read :search do read :search do
description "Return a list of Authors, optionally filtering by name."
argument :name, :ci_string do argument :name, :ci_string do
description "Return Authors, which names includes the name."
constraints allow_empty?: true constraints allow_empty?: true
default "" default ""
end end
@@ -21,8 +21,12 @@ defmodule DecentralisedBookIndex.Metadata.AuthorRole do
end end
end end
resource do
description "A metadata of an author's role."
end
actions do actions do
defaults [:read, :destroy] defaults [:destroy]
create :create do create :create do
primary? true primary? true
@@ -43,13 +47,20 @@ defmodule DecentralisedBookIndex.Metadata.AuthorRole do
end end
end end
read :read do
description "Return a list of AuthorRoles."
primary? true
end
read :by_id do read :by_id do
description "Return the AuthorRole by its id."
argument :id, :uuid, allow_nil?: false argument :id, :uuid, allow_nil?: false
get? true get? true
filter expr(id == ^arg(:id)) filter expr(id == ^arg(:id))
end end
read :by_book_id do read :by_book_id do
description "Return the AuthorRole by its BookId."
argument :book_id, :uuid, allow_nil?: false argument :book_id, :uuid, allow_nil?: false
filter expr(book_id == ^arg(:book_id)) filter expr(book_id == ^arg(:book_id))
end end
@@ -25,6 +25,10 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end end
end end
resource do
description "A book's metadata."
end
actions do actions do
defaults [:read, :destroy] defaults [:read, :destroy]
@@ -142,6 +146,7 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end end
read :by_id do read :by_id do
description "Return the Book by its id."
argument :id, :uuid, allow_nil?: false argument :id, :uuid, allow_nil?: false
get? true get? true
filter expr(id == ^arg(:id)) filter expr(id == ^arg(:id))
@@ -182,7 +187,10 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end end
read :search do read :search do
description "Return a list of Books, optionally filtering by title."
argument :query, :ci_string do argument :query, :ci_string do
description "Return Books, which titles includes the query."
constraints allow_empty?: true constraints allow_empty?: true
default "" default ""
end end
@@ -193,6 +201,8 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end end
read :search_by_bid do read :search_by_bid do
description "Return a list of Books, optionally filtering by BookId."
argument :type, :string, allow_nil?: false argument :type, :string, allow_nil?: false
argument :bid, :string, allow_nil?: false argument :bid, :string, allow_nil?: false
@@ -26,8 +26,12 @@ defmodule DecentralisedBookIndex.Metadata.BookId do
end end
end end
resource do
description "A book's identifier."
end
actions do actions do
defaults [:read, :destroy] defaults [:destroy]
create :create do create :create do
primary? true primary? true
@@ -39,7 +43,13 @@ defmodule DecentralisedBookIndex.Metadata.BookId do
accept [:id, :type, :bid, :order] accept [:id, :type, :bid, :order]
end end
read :read do
description "Return a list of BookIds."
primary? true
end
read :by_id do read :by_id do
description "Return the BookId by its id."
argument :id, :uuid, allow_nil?: false argument :id, :uuid, allow_nil?: false
get? true get? true
filter expr(id == ^arg(:id)) filter expr(id == ^arg(:id))
@@ -21,12 +21,16 @@ defmodule DecentralisedBookIndex.Metadata.DBIServer do
end end
end end
resource do
description "An DBIServer's metadata."
end
actions do actions do
defaults [:read, :destroy] defaults [:read, :destroy]
create :create do create :create do
primary? true primary? true
accept [:name, :url, :sync_on?] accept [:name, :url, :sync_on?, :dbi_server_id]
end end
create :sync_create do create :sync_create do
@@ -43,13 +47,17 @@ defmodule DecentralisedBookIndex.Metadata.DBIServer do
end end
read :by_id do read :by_id do
description "Return the DBIServer by its id."
argument :id, :uuid, allow_nil?: false argument :id, :uuid, allow_nil?: false
get? true get? true
filter expr(id == ^arg(:id)) filter expr(id == ^arg(:id))
end end
read :search do read :search do
description "Return a list of DBIServers, optionally filtering by name."
argument :name, :ci_string do argument :name, :ci_string do
description "Return DBIServers, which names includes the name."
constraints allow_empty?: true constraints allow_empty?: true
default "" default ""
end end
@@ -95,7 +103,9 @@ defmodule DecentralisedBookIndex.Metadata.DBIServer do
end end
relationships do 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, Metadata.Author
has_many :author_alias_registries, Metadata.AuthorAliasRegistry has_many :author_alias_registries, Metadata.AuthorAliasRegistry
@@ -22,26 +22,39 @@ defmodule DecentralisedBookIndex.Metadata.Publisher do
end end
end end
resource do
description "An publisher's metadata."
end
actions do actions do
defaults [:read, :destroy] defaults [:destroy]
create :create do create :create do
primary? true primary? true
accept [:name] accept [:name, :dbi_server_id]
end end
create :sync_create do create :sync_create do
accept [:id, :name, :inserted_at, :updated_at, :dbi_server_id] accept [:id, :name, :inserted_at, :updated_at, :dbi_server_id]
end end
read :read do
description "Return a list of Publishers."
primary? true
end
read :by_id do read :by_id do
description "Return the Publisher by its id."
argument :id, :uuid, allow_nil?: false argument :id, :uuid, allow_nil?: false
get? true get? true
filter expr(id == ^arg(:id)) filter expr(id == ^arg(:id))
end end
read :search do read :search do
description "Return a list of Publishers, optionally filtering by name."
argument :name, :ci_string do argument :name, :ci_string do
description "Return Publishers, which names includes the name."
constraints allow_empty?: true constraints allow_empty?: true
default "" default ""
end end
@@ -20,6 +20,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorTransformer do
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"]) 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
end end
@@ -26,6 +26,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookTransformer do
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"]) 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
end end
@@ -13,9 +13,15 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformer do
name: get_in(json_body, ["attributes", "name"]), name: get_in(json_body, ["attributes", "name"]),
url: get_in(json_body, ["attributes", "url"]), url: get_in(json_body, ["attributes", "url"]),
inserted_at: get_in(json_body, ["attributes", "inserted_at"]), 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
end end
@@ -17,6 +17,10 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.PublisherTransformer do
dbi_server_id: get_in(json_body, ["attributes", "dbi_server_id"]) 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
end end
@@ -11,48 +11,58 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Pagination do
assigns = assign(assigns, :current_page_number, get_current_page(assigns.page)) assigns = assign(assigns, :current_page_number, get_current_page(assigns.page))
~H""" ~H"""
<nav aria-label="Page navigation example" class="flex justify-center"> <%= if AshPhoenix.LiveView.prev_page?(@page) or AshPhoenix.LiveView.next_page?(@page) do %>
<ul class="inline-flex -space-x-px text-base h-10 mx-auto"> <nav aria-label="Page navigation example" class="flex justify-center">
<li class="pl-auto"> <ul class="inline-flex -space-x-px text-base h-10 mx-auto">
<.button_link <li class="pl-auto">
data-role="previous-page" <.button_link
kind="primary" data-role="previous-page"
inverse kind="primary"
patch={"#{@endpoint}?#{page_params_which(@page, @params, "prev")}"} inverse
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" patch={"#{@endpoint}?#{page_params_which(@page, @params, "prev")}"}
disabled={!AshPhoenix.LiveView.prev_page?(@page)} 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> Previous
</li> </.button_link>
<%= for number <- get_page_numbers(@page) do %> </li>
<%= if number == @current_page_number do %> <%= for number <- get_page_numbers(@page) do %>
<li> <%= if number == @current_page_number do %>
<a href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"} <li>
aria-current="page" <a
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> href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
</li> aria-current="page"
<% else %> 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"
<li> >
<a href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"} {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> </a>
</li> </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 %>
<% end %> <li>
<li> <.button_link
<.button_link data-role="next-page"
data-role="next-page" kind="primary"
kind="primary" inverse
inverse patch={"#{@endpoint}?#{page_params_which(@page, @params, "next")}"}
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"
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)}
disabled={!AshPhoenix.LiveView.next_page?(@page)} >
> Next
Next </.button_link>
</.button_link> </li>
</li> </ul>
</ul> </nav>
</nav> <% end %>
""" """
end end
@@ -64,7 +64,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SearchResources do
defp can_add?(current_user, role) do defp can_add?(current_user, role) do
case 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) :moderator -> current_user != nil and Role.can_moderate?(current_user.role)
_ -> false _ -> false
end end
@@ -20,8 +20,6 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SelectedAuthor do
errors = errors =
if Phoenix.Component.used_input?(assigns.author_form), do: assigns.author_form.errors, else: [] if Phoenix.Component.used_input?(assigns.author_form), do: assigns.author_form.errors, else: []
IO.inspect("errors")
IO.inspect(errors)
assigns = assigns =
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"> <span class="block text-sm text-gray-500 text-center dark:text-gray-400">
© 2025 Kostiantyn Klochko. All Rights Reserved. © 2025 Kostiantyn Klochko. All Rights Reserved.
</span> </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> </div>
</footer> </footer>
""" """
@@ -106,7 +106,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Navbar do
</a> </a>
</li> </li>
<% end %> <% 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> <li>
<a <a
href={~p"/servers/"} href={~p"/servers/"}
@@ -158,9 +158,6 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.FormComponent do
end end
def handle_event("select-author-alias", %{"author-alias" => author_alias_id}, socket) do 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 =
socket socket
|> update(:form, fn form -> |> update(:form, fn form ->
@@ -57,6 +57,25 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
<p class="text-gray-300 leading-relaxed"> <p class="text-gray-300 leading-relaxed">
{@author.description} {@author.description}
</p> </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>
</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> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{bid.type}: {bid.bid}</dd>
<% end %> <% end %>
</dl> </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> </div>
<%= if not Enum.empty?(@alternative_editions) do %> <%= 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[:name]} type="text" label="Name" />
<.input field={@form[:url]} type="text" label="Url" /> <.input field={@form[:url]} type="text" label="Url" />
<% end %> <% 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[:name]} type="text" label="Name" />
<.input field={@form[:url]} type="text" label="Url" /> <.input field={@form[:url]} type="text" label="Url" />
<% end %> <% end %>
@@ -61,7 +61,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.FormComponent do
socket = socket =
socket socket
|> put_flash(:info, "Server #{socket.assigns.form.source.type}d successfully") |> 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} {:noreply, socket}
@@ -4,26 +4,30 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Index do
alias DecentralisedBookIndex.Metadata alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.SyncTasks.SyncServerTask alias DecentralisedBookIndex.SyncTasks.SyncServerTask
alias DecentralisedBookIndex.Accounts.Role
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.header> <.header>
Listing Servers Listing Servers
<:actions> <: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"> <div class="flex flex-row gap-2">
<.primary_button phx-click="sync"> <div class="flex flex-row gap-2">
Sync now <.primary_button phx-click="sync">
</.primary_button> Sync now
</div>
<div class="flex flex-row gap-2">
<.link patch={~p"/oban"}>
<.primary_button>
Task dashboard
</.primary_button> </.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>
</div> <% end %>
</:actions> </:actions>
</.header> </.header>
@@ -63,7 +67,9 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Index do
<.link navigate={~p"/servers/#{dbi_server}"}>Show</.link> <.link navigate={~p"/servers/#{dbi_server}"}>Show</.link>
</div> </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> </:action>
</.table> </.table>
@@ -1,6 +1,8 @@
defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
use DecentralisedBookIndexWeb, :live_view use DecentralisedBookIndexWeb, :live_view
alias DecentralisedBookIndex.Accounts.Role
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
@@ -8,7 +10,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
{@dbi_server.name} {@dbi_server.name}
<:actions> <: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()}> <.link patch={~p"/servers/#{@dbi_server}/edit"} phx-click={JS.push_focus()}>
<.edit_button> <.edit_button>
Edit Edit
@@ -18,7 +20,6 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
</:actions> </:actions>
</.header> </.header>
<div> <div>
<dl> <dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Url</dt> <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> <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> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@dbi_server.sync_on?}</dd>
</dl> </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> </div>
<.back navigate={~p"/servers"}>Back to servers</.back> <.back navigate={~p"/servers"}>Back to servers</.back>
@@ -1,6 +1,8 @@
defmodule DecentralisedBookIndexWeb.PublisherLive.Show do defmodule DecentralisedBookIndexWeb.PublisherLive.Show do
use DecentralisedBookIndexWeb, :live_view use DecentralisedBookIndexWeb, :live_view
alias DecentralisedBookIndex.Accounts.Role
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
@@ -18,6 +20,27 @@ defmodule DecentralisedBookIndexWeb.PublisherLive.Show do
</:actions> </:actions>
</.header> </.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> <.back navigate={~p"/publishers"}>Back to publishers</.back>
""" """
end end
@@ -13,7 +13,7 @@ defmodule DecentralisedBookIndexWeb.Plugs.RequireAdmin do
conn conn
else else
conn conn
|> put_flash(:error, "Unauthorized") |> put_flash(:error, "Unauthorized!")
|> redirect(to: "/") |> redirect(to: "/")
|> halt() |> halt()
end end
+12 -10
View File
@@ -41,6 +41,18 @@ defmodule DecentralisedBookIndexWeb.Router do
scope "/", DecentralisedBookIndexWeb do scope "/", DecentralisedBookIndexWeb do
pipe_through :browser 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, ash_authentication_live_session :moderator_authenticated_routes,
on_mount: {DecentralisedBookIndexWeb.LiveUserAuth, :moderator_required} do on_mount: {DecentralisedBookIndexWeb.LiveUserAuth, :moderator_required} do
live "/books/new", BookLive.Edit, :new live "/books/new", BookLive.Edit, :new
@@ -56,19 +68,9 @@ defmodule DecentralisedBookIndexWeb.Router do
live "/publishers/:id/edit", PublisherLive.Edit, :edit live "/publishers/:id/edit", PublisherLive.Edit, :edit
live "/publishers/:id", PublisherLive.Show, :show 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", DbiServerLive.Index, :index
live "/servers/new", DbiServerLive.Edit, :new
live "/servers/:id/edit", DbiServerLive.Edit, :edit
live "/servers/:id", DbiServerLive.Show, :show 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 end
ash_authentication_live_session :maybe_authenticated_routes, ash_authentication_live_session :maybe_authenticated_routes,
+1 -1
View File
@@ -4,7 +4,7 @@ defmodule DecentralisedBookIndex.MixProject do
def project do def project do
[ [
app: :decentralised_book_index, app: :decentralised_book_index,
version: "0.1.0", version: "1.0.0",
elixir: "~> 1.14", elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
@@ -56,7 +56,7 @@ defmodule DecentralisedBookIndex.Metadata.AuthorTest do
test "new author has no alternatives names", %{user: user} do test "new author has no alternatives names", %{user: user} do
{: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 {:ok, alternatives_names} = Metadata.get_author_alternative_names(author) assert {:ok, alternatives_names} = Metadata.get_author_alternative_names(author)
assert alternatives_names = [] assert ^alternatives_names = []
end end
test "author has related author so they has one alternative name", %{user: user} do 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 {:ok, alternatives_names} = Metadata.get_author_alternative_names(author)
assert alternatives_names = [related_author] assert get_ids(alternatives_names) == [related_author.id]
end end
end end
@@ -118,7 +118,8 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
bids, bids,
author_roles, author_roles,
publisher.id, publisher.id,
nil nil,
actor: user
) )
end end
end end
@@ -146,7 +147,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
) )
assert {:ok, alternatives_names} = Metadata.get_book_alternative_editions(book) assert {:ok, alternatives_names} = Metadata.get_book_alternative_editions(book)
assert alternatives_names = [] assert ^alternatives_names = []
end end
test "book has related book so they has one alternative name", %{user: user} do 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 {:ok, alternatives_names} = Metadata.get_book_alternative_editions(book)
assert alternatives_names = [related_book] assert get_ids(alternatives_names) == [related_book.id]
end end
end end
@@ -201,7 +202,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
%{order: 1, author_id: author.id, role: ""} %{order: 1, author_id: author.id, role: ""}
] ]
{:ok, book} = {:ok, _book} =
Metadata.create_book( Metadata.create_book(
"Book", "Book",
"An description", "An description",
@@ -217,7 +218,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
actor: user actor: user
) )
assert {:ok, books} = Metadata.get_author_books(author) assert {:ok, _books} = Metadata.get_author_books(author)
end end
test "get the list contains aliases' books", %{user: user} do test "get the list contains aliases' books", %{user: user} do
@@ -256,7 +257,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
end end
describe "get a book by book id" do describe "get a book by book id" do
test "get by bid", %{user: user} do test "get by bid" do
expected_book = generate(book()) expected_book = generate(book())
[%Metadata.BookId{type: type, bid: bid} | _] = expected_book.bids [%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 test "user can't submit" do
user = generate(user()) user = generate(user())
valid_params = %{
"name" => "Oleh",
"description" => "A cool author"
}
assert_raise Ash.Error.Forbidden, fn -> assert_raise Ash.Error.Forbidden, fn ->
form = _form =
AshPhoenix.Form.for_create(Metadata.Author, :create, AshPhoenix.Form.for_create(Metadata.Author, :create,
as: "author", as: "author",
actor: user actor: user
@@ -28,12 +23,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
end end
test "moderator can submit", %{user: user} do test "moderator can submit", %{user: user} do
valid_params = %{ assert _form =
"name" => "Oleh",
"description" => "A cool author"
}
assert form =
AshPhoenix.Form.for_create(Metadata.Author, :create, AshPhoenix.Form.for_create(Metadata.Author, :create,
as: "author", as: "author",
actor: user actor: user
@@ -55,7 +45,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
actor: user 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 end
@@ -74,7 +64,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.AuthorFormTest do
actor: user 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 end
end end
@@ -4,7 +4,6 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
import ExUnitProperties import ExUnitProperties
alias DecentralisedBookIndex.Metadata alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.Metadata.Book
setup do setup do
user = generate(user(role: :moderator)) user = generate(user(role: :moderator))
@@ -14,22 +13,9 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
describe "form submiting" do describe "form submiting" do
test "user can't submit" do test "user can't submit" do
user = generate(user()) 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 -> assert_raise Ash.Error.Forbidden, fn ->
form = _form =
AshPhoenix.Form.for_create(Metadata.Book, :create, AshPhoenix.Form.for_create(Metadata.Book, :create,
as: "book", as: "book",
actor: user actor: user
@@ -39,21 +25,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
end end
test "moderator can submit", %{user: user} do test "moderator can submit", %{user: user} do
bids = bids(actor: user) assert _form =
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 =
AshPhoenix.Form.for_create(Metadata.Book, :create, AshPhoenix.Form.for_create(Metadata.Book, :create,
as: "book", as: "book",
actor: user actor: user
@@ -86,7 +58,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
actor: user actor: user
) )
assert {:ok, user} = AshPhoenix.Form.submit(form, params: valid_params) assert {:ok, _book} = AshPhoenix.Form.submit(form, params: valid_params)
end end
end end
@@ -106,7 +78,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
actor: user actor: user
) )
assert {:ok, user} = AshPhoenix.Form.submit(form, params: valid_params) assert {:ok, _book} = AshPhoenix.Form.submit(form, params: valid_params)
end end
test "update bids and authors", %{user: user} do test "update bids and authors", %{user: user} do
@@ -125,7 +97,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.BookFormTest do
actor: user actor: user
) )
assert {:ok, user} = AshPhoenix.Form.submit(form, params: valid_params) assert {:ok, _book} = AshPhoenix.Form.submit(form, params: valid_params)
end end
end end
@@ -12,12 +12,8 @@ defmodule DecentralisedBookIndex.Metadata.Forms.PublisherFormTest do
test "user can't submit" do test "user can't submit" do
user = generate(user()) user = generate(user())
valid_params = %{
"name" => "Publisher"
}
assert_raise Ash.Error.Forbidden, fn -> assert_raise Ash.Error.Forbidden, fn ->
form = _form =
AshPhoenix.Form.for_create(Metadata.Publisher, :create, AshPhoenix.Form.for_create(Metadata.Publisher, :create,
as: "publisher", as: "publisher",
actor: user actor: user
@@ -27,11 +23,7 @@ defmodule DecentralisedBookIndex.Metadata.Forms.PublisherFormTest do
end end
test "moderator can submit", %{user: user} do test "moderator can submit", %{user: user} do
valid_params = %{ assert _form =
"name" => "Publisher"
}
assert form =
AshPhoenix.Form.for_create(Metadata.Publisher, :create, AshPhoenix.Form.for_create(Metadata.Publisher, :create,
as: "publisher", as: "publisher",
actor: user actor: user
@@ -81,5 +81,40 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorTransformerTest do
updated_at: "2025-03-26T10:16:16.124581Z" updated_at: "2025-03-26T10:16:16.124581Z"
} = author } = author
end 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
end end
@@ -138,5 +138,70 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookTransformerTest do
dbi_server_id: "889a323e-d104-4b5d-b276-dad5a9b1da99" dbi_server_id: "889a323e-d104-4b5d-b276-dad5a9b1da99"
} = book } = book
end 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
end end
@@ -8,6 +8,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
json_body = %{ json_body = %{
"data" => %{ "data" => %{
"attributes" => %{ "attributes" => %{
"dbi_server_id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
"inserted_at" => "2025-03-22T20:07:30.766249Z", "inserted_at" => "2025-03-22T20:07:30.766249Z",
"name" => "Test", "name" => "Test",
"updated_at" => "2025-05-04T18:48:44.213309Z", "updated_at" => "2025-05-04T18:48:44.213309Z",
@@ -16,7 +17,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
"id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc", "id" => "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
"links" => %{}, "links" => %{},
"meta" => %{}, "meta" => %{},
"relationships" => %{}, "relationships" => %{"dbi_server" => %{"links" => %{}, "meta" => %{}}},
"type" => "dbi_server" "type" => "dbi_server"
}, },
"jsonapi" => %{"version" => "1.0"}, "jsonapi" => %{"version" => "1.0"},
@@ -34,11 +35,40 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerTransformerTest
name: "Test", name: "Test",
url: "http://localhost:4001", url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z", 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 } = server
end end
test "a json doesn't contains server information \"data\" attribute" do 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 = %{ json_body = %{
"attributes" => %{ "attributes" => %{
"inserted_at" => "2025-03-22T20:07:30.766249Z", "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", inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z" updated_at: "2025-05-04T18:48:44.213309Z"
} = server } = server
refute Map.has_key?(server, :dbi_server_id)
end end
end end
end end
@@ -63,5 +63,32 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.PublisherTransformerTest
dbi_server_id: "889a323e-d104-4b5d-b276-dad5a9b1da99" dbi_server_id: "889a323e-d104-4b5d-b276-dad5a9b1da99"
} = publisher } = publisher
end 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
end end
@@ -26,17 +26,13 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.AuthorSyncTest do
updated_at: "2025-03-21T09:20:48.791539Z" 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 = AuthorSync.create_update(author, server.id)
assert {:ok, saved_author} = Metadata.get_author_by_id(author.id) assert {:ok, saved_author} = Metadata.get_author_by_id(author.id)
author = author =
author author
|> Map.replace(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_at)
|> Map.delete(:author_alias_registry_id) |> Map.delete(:author_alias_registry_id)
|> datetime_from_iso8601_for_map()
assert get_submap(saved_author, author) == author assert get_submap(saved_author, author) == author
assert nil != saved_author.author_alias_registry_id 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" 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 = AuthorSync.create_update(author, server.id)
assert {:ok, saved_author} = Metadata.get_author_by_id(author.id) assert {:ok, saved_author} = Metadata.get_author_by_id(author.id)
author = author =
author author
|> Map.replace(:inserted_at, inserted_at) |> datetime_from_iso8601_for_map()
|> Map.replace(:updated_at, updated_at)
assert get_submap(saved_author, author) == author assert get_submap(saved_author, author) == author
assert nil != saved_author.author_alias_registry_id 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 = AuthorSync.create_update(author_attrs, server.id)
assert {:ok, saved_author} = Metadata.get_author_by_id(author.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 end
test "the local existing author wouldn't updated", %{user: user} do 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" 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 = BookSync.create_update(book, server.id)
assert {:ok, saved_book} = Metadata.get_book_by_id(book.id) assert {:ok, saved_book} = Metadata.get_book_by_id(book.id)
book = book =
book book
|> Map.replace(:inserted_at, inserted_at) |> datetime_from_iso8601_for_map()
|> Map.replace(:updated_at, updated_at)
|> Map.replace(:published, Date.from_iso8601!(book[:published])) |> Map.replace(:published, Date.from_iso8601!(book[:published]))
assert nil != saved_book.book_editions_registry_id assert nil != saved_book.book_editions_registry_id
assert server.id == saved_book.dbi_server_id assert server.id == saved_book.dbi_server_id
assert get_submap(saved_book, book) == book
end end
test "a new book has the same editions registry" do 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" 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 = BookSync.create_update(book, server.id)
assert {:ok, saved_book} = Metadata.get_book_by_id(book.id) assert {:ok, saved_book} = Metadata.get_book_by_id(book.id)
book = book =
book book
|> Map.replace(:inserted_at, inserted_at) |> datetime_from_iso8601_for_map()
|> Map.replace(:updated_at, updated_at)
|> Map.replace(:published, Date.from_iso8601!(book[:published])) |> Map.replace(:published, Date.from_iso8601!(book[:published]))
assert nil != saved_book.book_editions_registry_id assert nil != saved_book.book_editions_registry_id
assert server.id == saved_book.dbi_server_id assert server.id == saved_book.dbi_server_id
assert get_submap(saved_book, book) == book
end end
test "a new book with relationships will be created" do 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 = 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 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)) server = generate(dbi_server(url: @test_server_endpoint))
book = generate(book()) book = generate(book())
@@ -195,7 +189,7 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.BookSyncTest do
assert saved_book.dbi_server_id == nil assert saved_book.dbi_server_id == nil
end 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)) server = generate(dbi_server(url: @test_server_endpoint))
book = generate(book()) 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) assert Enum.all?(old_roles, fn id -> id not in new_roles end)
end 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 end
@@ -17,11 +17,11 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.DBIServerSyncTest do
server = generate(dbi_server(url: @test_server_endpoint)) server = generate(dbi_server(url: @test_server_endpoint))
dbi_server = %{ dbi_server = %{
id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc", id: "0c0647ec-07ef-4caa-b683-5847dbfbe5cc",
name: "Test", name: "Test",
url: "http://localhost:4001", url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z", inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z" updated_at: "2025-05-04T18:48:44.213309Z"
} }
{:ok, inserted_at, 0} = DateTime.from_iso8601(dbi_server[:inserted_at]) {: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(:inserted_at, inserted_at)
|> Map.replace(:updated_at, updated_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 assert server.id == saved_dbi_server.dbi_server_id
end end
test "update an existing dbi_server", %{user: user} do test "update an existing dbi_server", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint)) 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 = %{ dbi_server_attrs = %{
id: dbi_server.id, id: dbi_server.id,
name: "Test", name: "Test",
url: "http://localhost:4001", url: "http://localhost:4001",
inserted_at: "2025-03-22T20:07:30.766249Z", inserted_at: "2025-03-22T20:07:30.766249Z",
updated_at: "2025-05-04T18:48:44.213309Z" updated_at: "2025-05-04T18:48:44.213309Z"
} }
assert :ok = DBIServerSync.create_update(dbi_server_attrs, server.id) 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 {: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 assert server.id == saved_dbi_server.dbi_server_id
end end
end end
@@ -23,9 +23,6 @@ defmodule DecentralisedBookIndex.Sync.DataTransformers.PublisherSyncTest do
updated_at: "2025-03-21T09:20:48.791539Z" 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 = PublisherSync.create_update(publisher, server.id)
assert {:ok, saved_publisher} = Metadata.get_publisher_by_id(publisher.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 test "sync authors", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint)) 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)
{:ok, author} = Metadata.create_author("Author2", "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 end
end end
@@ -2,7 +2,6 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncBookTaskTest do
use DecentralisedBookIndex.DataCase use DecentralisedBookIndex.DataCase
alias DecentralisedBookIndex.SyncTasks.SyncBooksTask alias DecentralisedBookIndex.SyncTasks.SyncBooksTask
alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.TestEndpoints alias DecentralisedBookIndex.TestEndpoints
@test_server_endpoint TestEndpoints.test_api_endpoint() @test_server_endpoint TestEndpoints.test_api_endpoint()
@@ -14,7 +13,7 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncBookTaskTest do
_book = generate(book()) _book = generate(book())
_book = generate(book()) _book = generate(book())
assert server = SyncBooksTask.sync(server) assert ^server = SyncBooksTask.sync(server)
end end
end 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("Test", "http://localhost:4001", false, actor: user)
{:ok, _dbi_server} = Metadata.create_dbi_server("Test2", "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 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("Publisher", actor: user)
{:ok, _publisher} = Metadata.create_publisher("Publisher2", actor: user) {:ok, _publisher} = Metadata.create_publisher("Publisher2", actor: user)
assert server = SyncPublishersTask.sync(server) assert ^server = SyncPublishersTask.sync(server)
end end
end end
end end
@@ -3,9 +3,6 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncServerTaskTest do
alias DecentralisedBookIndex.SyncTasks.SyncServerTask alias DecentralisedBookIndex.SyncTasks.SyncServerTask
alias DecentralisedBookIndex.Sync.ApiClients.FetchJson
alias DecentralisedBookIndex.Sync.AuthorSync
alias DecentralisedBookIndex.Metadata alias DecentralisedBookIndex.Metadata
alias DecentralisedBookIndex.TestEndpoints alias DecentralisedBookIndex.TestEndpoints
@@ -18,19 +15,19 @@ defmodule DecentralisedBookIndex.SyncTasks.SyncServerTaskTest do
describe "sync all" do describe "sync all" do
test "servers", %{user: user} do test "servers", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint)) _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_all() assert :ok = SyncServerTask.sync_all()
end end
end end
describe "sync one" do describe "sync one" do
test "server", %{user: user} do test "server", %{user: user} do
server = generate(dbi_server(url: @test_server_endpoint)) 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 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)) generate(user(role: :moderator))
end) end)
dbi_server_id = opts[:dbi_server_id] || nil
changeset_generator( changeset_generator(
Metadata.Author, Metadata.Author,
:create, :create,
defaults: [ defaults: [
name: sequence(:name, &"Author #{&1}"), name: sequence(:name, &"Author #{&1}"),
description: sequence(:name, &"Description #{&1}"), description: sequence(:name, &"Description #{&1}"),
author_alias_registry_id: nil author_alias_registry_id: nil,
dbi_server_id: dbi_server_id
], ],
overrides: opts, overrides: opts,
actor: actor actor: actor
@@ -83,16 +86,17 @@ defmodule DecentralisedBookIndex.Generators do
url = url =
opts[:url] || opts[:url] ||
once(:default_actor, fn -> "http://localhost:4002"
"http://localhost:4002"
end) dbi_server_id = opts[:dbi_server_id] || nil
changeset_generator( changeset_generator(
Metadata.DBIServer, Metadata.DBIServer,
:create, :create,
defaults: [ defaults: [
name: sequence(:name, &"Server #{&1}"), name: sequence(:name, &"Server #{&1}"),
url: url url: url,
dbi_server_id: dbi_server_id
], ],
overrides: opts, overrides: opts,
actor: actor actor: actor
@@ -136,11 +140,14 @@ defmodule DecentralisedBookIndex.Generators do
generate(user(role: :moderator)) generate(user(role: :moderator))
end) end)
dbi_server_id = opts[:dbi_server_id] || nil
changeset_generator( changeset_generator(
Metadata.Publisher, Metadata.Publisher,
:create, :create,
defaults: [ defaults: [
name: sequence(:name, &"Publisher #{&1}") name: sequence(:name, &"Publisher #{&1}"),
dbi_server_id: dbi_server_id
], ],
overrides: opts, overrides: opts,
actor: actor actor: actor
+18
View File
@@ -40,4 +40,22 @@ defmodule DecentralisedBookIndex.Helpers do
|> DateTime.add(seconds) |> DateTime.add(seconds)
|> DateTime.to_iso8601() |> DateTime.to_iso8601()
end 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 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