From 9cdedf0c7c393aa46091875a5125e8f17ccf5645 Mon Sep 17 00:00:00 2001 From: KKlochko Date: Sun, 9 Mar 2025 22:02:18 +0200 Subject: [PATCH] Add AuthorRole to manage relationship between Author and Book. --- lib/decentralised_book_index/metadata.ex | 6 +- .../metadata/author.ex | 7 +- .../metadata/author_role.ex | 49 +++++++ lib/decentralised_book_index/metadata/book.ex | 15 ++- ...e_author_role_and_update_relationships.exs | 75 +++++++++++ ...allow_empty_role_and_book_relationship.exs | 23 ++++ .../repo/author_role/20250309192029.json | 98 ++++++++++++++ .../repo/author_role/20250309194121.json | 98 ++++++++++++++ .../repo/authors/20250309192029.json | 127 ++++++++++++++++++ 9 files changed, 494 insertions(+), 4 deletions(-) create mode 100644 lib/decentralised_book_index/metadata/author_role.ex create mode 100644 priv/repo/migrations/20250309192029_create_author_role_and_update_relationships.exs create mode 100644 priv/repo/migrations/20250309194121_update_author_role_to_allow_empty_role_and_book_relationship.exs create mode 100644 priv/resource_snapshots/repo/author_role/20250309192029.json create mode 100644 priv/resource_snapshots/repo/author_role/20250309194121.json create mode 100644 priv/resource_snapshots/repo/authors/20250309192029.json diff --git a/lib/decentralised_book_index/metadata.ex b/lib/decentralised_book_index/metadata.ex index 5c1c6ff..e8000e6 100644 --- a/lib/decentralised_book_index/metadata.ex +++ b/lib/decentralised_book_index/metadata.ex @@ -6,11 +6,11 @@ defmodule DecentralisedBookIndex.Metadata do resource DecentralisedBookIndex.Metadata.Book do define :create_book, action: :create, - args: [:title, :isbn, :description, {:optional, :book_editions_registry_id}] + args: [:title, :isbn, :description, :author_roles, {:optional, :book_editions_registry_id}] define :add_book_to_related_editions_registry, action: :add_book_to_related_editions_registry, - args: [:title, :isbn, :description, :related_book_id] + args: [:title, :isbn, :description, :author_roles, :related_book_id] define :list_books, action: :read define :get_book_by_id, args: [:id], action: :by_id @@ -52,5 +52,7 @@ defmodule DecentralisedBookIndex.Metadata do define :update_book_editions_registry, action: :update define :destroy_book_editions_registry, action: :destroy end + + resource DecentralisedBookIndex.Metadata.AuthorRole end end diff --git a/lib/decentralised_book_index/metadata/author.ex b/lib/decentralised_book_index/metadata/author.ex index 57366e9..478a2ab 100644 --- a/lib/decentralised_book_index/metadata/author.ex +++ b/lib/decentralised_book_index/metadata/author.ex @@ -5,6 +5,7 @@ defmodule DecentralisedBookIndex.Metadata.Author do data_layer: AshPostgres.DataLayer require Ash.Query + alias DecentralisedBookIndex.Metadata postgres do table "authors" @@ -87,7 +88,11 @@ defmodule DecentralisedBookIndex.Metadata.Author do end relationships do - belongs_to :author_alias_registry, DecentralisedBookIndex.Metadata.AuthorAliasRegistry do + belongs_to :author_alias_registry, Metadata.AuthorAliasRegistry do + attribute_writable? true + end + + belongs_to :author_role, Metadata.AuthorRole do attribute_writable? true end end diff --git a/lib/decentralised_book_index/metadata/author_role.ex b/lib/decentralised_book_index/metadata/author_role.ex new file mode 100644 index 0000000..670e7e2 --- /dev/null +++ b/lib/decentralised_book_index/metadata/author_role.ex @@ -0,0 +1,49 @@ +defmodule DecentralisedBookIndex.Metadata.AuthorRole do + use Ash.Resource, + otp_app: :decentralised_book_index, + domain: DecentralisedBookIndex.Metadata, + data_layer: AshPostgres.DataLayer + + alias DecentralisedBookIndex.Metadata + + postgres do + table "author_role" + repo DecentralisedBookIndex.Repo + end + + actions do + defaults [:read, :update, :destroy] + + create :create do + primary? true + accept [:order, :role] + argument :author_id, :uuid + end + end + + attributes do + uuid_primary_key :id + + attribute :role, :string do + allow_nil? true + public? true + end + + attribute :order, :integer do + allow_nil? false + public? true + end + + timestamps() + end + + relationships do + belongs_to :book, Metadata.Book do + allow_nil? true + end + + has_one :author, Metadata.Author do + allow_nil? false + end + end +end diff --git a/lib/decentralised_book_index/metadata/book.ex b/lib/decentralised_book_index/metadata/book.ex index 8b970b5..77c4d6c 100644 --- a/lib/decentralised_book_index/metadata/book.ex +++ b/lib/decentralised_book_index/metadata/book.ex @@ -5,6 +5,7 @@ defmodule DecentralisedBookIndex.Metadata.Book do data_layer: AshPostgres.DataLayer require Ash.Query + alias DecentralisedBookIndex.Metadata postgres do table "books" @@ -17,6 +18,7 @@ defmodule DecentralisedBookIndex.Metadata.Book do create :create do primary? true accept [:title, :isbn, :description, :book_editions_registry_id] + argument :author_roles, {:array, :map} change fn changeset, _ -> registry_id = Ash.Changeset.get_attribute(changeset, :book_editions_registry_id) @@ -29,6 +31,8 @@ defmodule DecentralisedBookIndex.Metadata.Book do changeset end end + + change manage_relationship(:author_roles, type: :direct_control, order_is_key: :order) end create :add_book_to_related_editions_registry do @@ -38,6 +42,8 @@ defmodule DecentralisedBookIndex.Metadata.Book do allow_nil? false end + argument :author_roles, {:array, :map} + change fn changeset, context -> related_book_id = changeset.arguments.related_book_id @@ -49,6 +55,8 @@ defmodule DecentralisedBookIndex.Metadata.Book do Ash.Changeset.force_change_attribute(changeset, :book_editions_registry_id, related_book.book_editions_registry_id) end end + + change manage_relationship(:author_roles, type: :direct_control, order_is_key: :order) end read :by_id do @@ -92,6 +100,11 @@ defmodule DecentralisedBookIndex.Metadata.Book do end relationships do - belongs_to :book_editions_registry, DecentralisedBookIndex.Metadata.BookEditionsRegistry + belongs_to :book_editions_registry, Metadata.BookEditionsRegistry + + has_many :author_roles, Metadata.AuthorRole do + sort order: :asc + public? true + end end end diff --git a/priv/repo/migrations/20250309192029_create_author_role_and_update_relationships.exs b/priv/repo/migrations/20250309192029_create_author_role_and_update_relationships.exs new file mode 100644 index 0000000..342ac2f --- /dev/null +++ b/priv/repo/migrations/20250309192029_create_author_role_and_update_relationships.exs @@ -0,0 +1,75 @@ +defmodule DecentralisedBookIndex.Repo.Migrations.CreateAuthorRoleAndUpdateRelationships do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + alter table(:authors) do + add :author_role_id, :uuid + end + + create table(:author_role, primary_key: false) do + add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true + end + + alter table(:authors) do + modify :author_role_id, + references(:author_role, + column: :id, + name: "authors_author_role_id_fkey", + type: :uuid, + prefix: "public" + ) + end + + alter table(:author_role) do + add :role, :text, null: false + add :order, :bigint, null: false + + add :inserted_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + + add :updated_at, :utc_datetime_usec, + null: false, + default: fragment("(now() AT TIME ZONE 'utc')") + + add :book_id, + references(:books, + column: :id, + name: "author_role_book_id_fkey", + type: :uuid, + prefix: "public" + ), + null: false + end + end + + def down do + drop constraint(:author_role, "author_role_book_id_fkey") + + alter table(:author_role) do + remove :book_id + remove :updated_at + remove :inserted_at + remove :order + remove :role + end + + drop constraint(:authors, "authors_author_role_id_fkey") + + alter table(:authors) do + modify :author_role_id, :uuid + end + + drop table(:author_role) + + alter table(:authors) do + remove :author_role_id + end + end +end diff --git a/priv/repo/migrations/20250309194121_update_author_role_to_allow_empty_role_and_book_relationship.exs b/priv/repo/migrations/20250309194121_update_author_role_to_allow_empty_role_and_book_relationship.exs new file mode 100644 index 0000000..744eb88 --- /dev/null +++ b/priv/repo/migrations/20250309194121_update_author_role_to_allow_empty_role_and_book_relationship.exs @@ -0,0 +1,23 @@ +defmodule DecentralisedBookIndex.Repo.Migrations.UpdateAuthorRoleToAllowEmptyRoleAndBookRelationship do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + alter table(:author_role) do + modify :book_id, :uuid, null: true + modify :role, :text, null: true + end + end + + def down do + alter table(:author_role) do + modify :role, :text, null: false + modify :book_id, :uuid, null: false + end + end +end diff --git a/priv/resource_snapshots/repo/author_role/20250309192029.json b/priv/resource_snapshots/repo/author_role/20250309192029.json new file mode 100644 index 0000000..8a42f0a --- /dev/null +++ b/priv/resource_snapshots/repo/author_role/20250309192029.json @@ -0,0 +1,98 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "order", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "author_role_book_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "books" + }, + "size": null, + "source": "book_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "6667F3DE0BDFD517C8D53D17E346C56035AC13788FC75DA5BB995D2C667C65DA", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.DecentralisedBookIndex.Repo", + "schema": null, + "table": "author_role" +} \ No newline at end of file diff --git a/priv/resource_snapshots/repo/author_role/20250309194121.json b/priv/resource_snapshots/repo/author_role/20250309194121.json new file mode 100644 index 0000000..a4ef40c --- /dev/null +++ b/priv/resource_snapshots/repo/author_role/20250309194121.json @@ -0,0 +1,98 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "role", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "order", + "type": "bigint" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "author_role_book_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "books" + }, + "size": null, + "source": "book_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "6724F84DA6E412084BDE66C28F5D25E079DA1A0CE11846D36C8FC8FEDBC080C1", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.DecentralisedBookIndex.Repo", + "schema": null, + "table": "author_role" +} \ No newline at end of file diff --git a/priv/resource_snapshots/repo/authors/20250309192029.json b/priv/resource_snapshots/repo/authors/20250309192029.json new file mode 100644 index 0000000..a4ec46f --- /dev/null +++ b/priv/resource_snapshots/repo/authors/20250309192029.json @@ -0,0 +1,127 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"gen_random_uuid()\")", + "generated?": false, + "primary_key?": true, + "references": null, + "size": null, + "source": "id", + "type": "uuid" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "name", + "type": "text" + }, + { + "allow_nil?": false, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "description", + "type": "text" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "inserted_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": false, + "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", + "generated?": false, + "primary_key?": false, + "references": null, + "size": null, + "source": "updated_at", + "type": "utc_datetime_usec" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "authors_author_alias_registry_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "author_alias_registries" + }, + "size": null, + "source": "author_alias_registry_id", + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "primary_key?": false, + "references": { + "deferrable": false, + "destination_attribute": "id", + "destination_attribute_default": null, + "destination_attribute_generated": null, + "index?": false, + "match_type": null, + "match_with": null, + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "name": "authors_author_role_id_fkey", + "on_delete": null, + "on_update": null, + "primary_key?": true, + "schema": "public", + "table": "author_role" + }, + "size": null, + "source": "author_role_id", + "type": "uuid" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "custom_statements": [], + "has_create_action": true, + "hash": "AC2913B21E513141F73B16ACEF1CD7D895075358BAC642373241092D464C2D58", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.DecentralisedBookIndex.Repo", + "schema": null, + "table": "authors" +} \ No newline at end of file