commit b1b5bbdd73b3f46e6826ce34005024208ce63f21
parent a25a4b94b528d7a389d9fcce60052bcdb25b17be
Author: srfsh <dev@srf.sh>
Date: Tue, 23 Aug 2022 08:14:31 +0300
Zenflows{Test,}.VF.Organization: add paging support and small improvements
Diffstat:
6 files changed, 132 insertions(+), 130 deletions(-)
diff --git a/src/zenflows/vf/organization.ex b/src/zenflows/vf/organization.ex
@@ -29,6 +29,7 @@ alias Zenflows.VF.{SpatialThing, Validate}
note: String.t() | nil,
primary_location: SpatialThing.t() | nil,
classified_as: [String.t()] | nil,
+ updated_at: DateTime.t(),
}
schema "vf_agent" do
diff --git a/src/zenflows/vf/organization/domain.ex b/src/zenflows/vf/organization/domain.ex
@@ -22,43 +22,51 @@ import Ecto.Query
alias Ecto.Multi
alias Zenflows.DB.Repo
+alias Zenflows.GQL.Paging
alias Zenflows.VF.Organization
@typep repo() :: Ecto.Repo.t()
@typep chgset() :: Ecto.Changeset.t()
-@typep changes() :: Ecto.Multi.changes()
@typep id() :: Zenflows.DB.Schema.id()
@typep params() :: Zenflows.DB.Schema.params()
-@spec by_id(repo(), id()) :: Organization.t() | nil
-def by_id(repo \\ Repo, id) do
- repo.get_by(Organization, id: id, type: :org)
+@spec one(repo(), id() | map() | Keyword.t())
+ :: {:ok, Organization.t()} | {:error, String.t()}
+def one(repo \\ Repo, _)
+def one(repo, id) when is_binary(id), do: one(repo, id: id)
+def one(repo, clauses) do
+ clauses = if(is_map(clauses),
+ do: Map.put(clauses, :type, :org),
+ else: Keyword.put(clauses, :type, :org))
+ case repo.get_by(Organization, clauses) do
+ nil -> {:error, "not found"}
+ found -> {:ok, found}
+ end
end
-@spec all() :: [Organization.t()]
-def all() do
- Organization
- |> from()
- |> where(type: :org)
- |> Repo.all()
+@spec all(Paging.params()) :: Paging.result(Organization.t())
+def all(params) do
+ Paging.page(where(Organization, type: :org), params)
end
@spec create(params()) :: {:ok, Organization.t()} | {:error, chgset()}
def create(params) do
Multi.new()
- |> Multi.insert(:org, Organization.chgset(params))
+ |> Multi.insert(:insert, Organization.chgset(params))
|> Repo.transaction()
|> case do
- {:ok, %{org: o}} -> {:ok, o}
+ {:ok, %{insert: o}} -> {:ok, o}
{:error, _, cset, _} -> {:error, cset}
end
end
-@spec update(id(), params()) :: {:ok, Organization.t()} | {:error, String.t() | chgset()}
+@spec update(id(), params())
+ :: {:ok, Organization.t()} | {:error, String.t() | chgset()}
def update(id, params) do
Multi.new()
- |> Multi.run(:get, multi_get(id))
- |> Multi.update(:update, &Organization.chgset(&1.get, params))
+ |> Multi.put(:id, id)
+ |> Multi.run(:one, &one/2)
+ |> Multi.update(:update, &Organization.chgset(&1.one, params))
|> Repo.transaction()
|> case do
{:ok, %{update: o}} -> {:ok, o}
@@ -69,8 +77,9 @@ end
@spec delete(id()) :: {:ok, Organization.t()} | {:error, String.t() | chgset()}
def delete(id) do
Multi.new()
- |> Multi.run(:get, multi_get(id))
- |> Multi.delete(:delete, &(&1.get))
+ |> Multi.put(:id, id)
+ |> Multi.run(:one, &one/2)
+ |> Multi.delete(:delete, &(&1.one))
|> Repo.transaction()
|> case do
{:ok, %{delete: o}} -> {:ok, o}
@@ -82,16 +91,4 @@ end
def preload(org, :primary_location) do
Repo.preload(org, :primary_location)
end
-
-# Returns an Organization in ok-err tuple from given ID. Used inside
-# Ecto.Multi.run/5 to get a record in transaction.
-@spec multi_get(id()) :: (repo(), changes() -> {:ok, Organization.t()} | {:error, String.t()})
-defp multi_get(id) do
- fn repo, _ ->
- case by_id(repo, id) do
- nil -> {:error, "not found"}
- org -> {:ok, org}
- end
- end
-end
end
diff --git a/src/zenflows/vf/organization/resolv.ex b/src/zenflows/vf/organization/resolv.ex
@@ -20,36 +20,40 @@ defmodule Zenflows.VF.Organization.Resolv do
alias Zenflows.VF.{Agent, Organization, Organization.Domain}
-def organization(%{id: id}, _info) do
- {:ok, Domain.by_id(id)}
+def organization(params, _) do
+ Domain.one(params)
end
-def create_organization(%{organization: params}, _info) do
+def organizations(params, _) do
+ Domain.all(params)
+end
+
+def create_organization(%{organization: params}, _) do
with {:ok, org} <- Domain.create(params) do
{:ok, %{agent: org}}
end
end
-def update_organization(%{organization: %{id: id} = params}, _info) do
+def update_organization(%{organization: %{id: id} = params}, _) do
with {:ok, org} <- Domain.update(id, params) do
{:ok, %{agent: org}}
end
end
-def delete_organization(%{id: id}, _info) do
+def delete_organization(%{id: id}, _) do
with {:ok, _} <- Domain.delete(id) do
{:ok, true}
end
end
-def primary_location(%Organization{} = org, _args, _info) do
+def primary_location(%Organization{} = org, _, _) do
org = Domain.preload(org, :primary_location)
{:ok, org.primary_location}
end
# For some reason, Absinthe calls this one instead of the one on
# Zenflows.VF.Agent.Type for queries to Agent itself.
-def primary_location(%Agent{} = agent, args, info) do
- Agent.Resolv.primary_location(agent, args, info)
+def primary_location(%Agent{} = agent, params, info) do
+ Agent.Resolv.primary_location(agent, params, info)
end
end
diff --git a/src/zenflows/vf/organization/type.ex b/src/zenflows/vf/organization/type.ex
@@ -33,6 +33,7 @@ occur and mail can be sent. This is usually a mappable geographic
location. It also could be a website address, as in the case of agents
who have no physical location.
"""
+@primary_location_id "(`SpatialThing`) #{@primary_location}"
@note "A textual description or comment."
@classified_as """
References one or more concepts in a common taxonomy or other
@@ -62,10 +63,6 @@ object :organization do
field :classified_as, list_of(non_null(:string))
end
-object :organization_response do
- field :agent, non_null(:organization)
-end
-
input_object :organization_create_params do
@desc @name
field :name, non_null(:string)
@@ -76,10 +73,7 @@ input_object :organization_create_params do
@desc @note
field :note, :string
- # TODO: When
- # https://github.com/absinthe-graphql/absinthe/issues/1126 results,
- # apply the correct changes if any.
- @desc "(`SpatialThing`) " <> @primary_location
+ @desc @primary_location_id
field :primary_location_id, :id, name: "primary_location"
@desc @classified_as
@@ -98,13 +92,27 @@ input_object :organization_update_params do
@desc @note
field :note, :string
- @desc "(`SpatialThing`) " <> @primary_location
+ @desc @primary_location_id
field :primary_location_id, :id, name: "primary_location"
@desc @classified_as
field :classified_as, list_of(non_null(:string))
end
+object :organization_response do
+ field :agent, non_null(:organization)
+end
+
+object :organization_edge do
+ field :cursor, non_null(:id)
+ field :node, non_null(:organization)
+end
+
+object :organization_connection do
+ field :page_info, non_null(:page_info)
+ field :edges, non_null(list_of(non_null(:organization_edge)))
+end
+
object :query_organization do
@desc "Find an organization (group) agent by its ID."
field :organization, :organization do
@@ -112,8 +120,17 @@ object :query_organization do
resolve &Resolv.organization/2
end
- #"Loads all organizations publicly registered within this collaboration space"
- #organizations(start: ID, limit: Int): [Organization!]
+ @desc """
+ Loads all organizations publicly registered within this
+ collaboration space.
+ """
+ field :organizations, :organization_connection do
+ arg :first, :integer
+ arg :after, :id
+ arg :last, :integer
+ arg :before, :id
+ resolve &Resolv.organizations/2
+ end
end
object :mutation_organization do
diff --git a/test/vf/organization/domain.test.exs b/test/vf/organization/domain.test.exs
@@ -21,68 +21,52 @@ use ZenflowsTest.Help.EctoCase, async: true
alias Ecto.Changeset
alias Zenflows.VF.{Organization, Organization.Domain}
-setup ctx do
- if ctx[:no_insert] do
- :ok
- else
- params = %{
+setup do
+ %{
+ params: %{
name: Factory.uniq("name"),
image: Factory.img(),
classified_as: Factory.uniq_list("uri"),
note: Factory.uniq("note"),
primary_location_id: Factory.insert!(:spatial_thing).id,
- }
-
- %{params: params, org: Factory.insert!(:organization)}
- end
+ },
+ inserted: Factory.insert!(:organization),
+ }
end
-describe "by_id/1" do
- test "returns an Organization", %{org: org} do
- assert %Organization{type: :org} = Domain.by_id(org.id)
+describe "one/1" do
+ test "with good id: finds the Organization", %{inserted: %{id: id}} do
+ assert {:ok, %Organization{}} = Domain.one(id)
end
- test "doesn't return a Person" do
+ test "with per's id: doesn't return a Person" do
per = Factory.insert!(:person)
-
- assert Domain.by_id(per.id) == nil
+ assert {:error, "not found"} = Domain.one(per.id)
end
-end
-
-@tag :no_insert
-test "all/0 returns all Organizations" do
- want_ids =
- Enum.map(1..10, fn _ -> Factory.insert!(:organization).id end)
- |> Enum.sort()
- have_ids =
- Domain.all()
- |> Enum.map(& &1.id)
- |> Enum.sort()
- assert have_ids == want_ids
+ test "with bad id: doesn't find the Organization" do
+ assert {:error, "not found"} = Domain.one(Factory.id())
+ end
end
describe "create/1" do
- test "creates an Organization with valid params", %{params: params} do
- assert {:ok, %Organization{} = org} = Domain.create(params)
-
- assert org.type == :org
- assert org.name == params.name
- assert org.image == params.image
- assert org.classified_as == params.classified_as
- assert org.note == params.note
- assert org.primary_location_id == params.primary_location_id
- end
+ test "with good params: creates an Organization", %{params: params} do
+ assert {:ok, %Organization{} = new} = Domain.create(params)
+ assert new.type == :org
+ assert new.name == params.name
+ assert new.image == params.image
+ assert new.classified_as == params.classified_as
+ assert new.note == params.note
+ assert new.primary_location_id == params.primary_location_id end
- test "doesn't create an Organization with invalid params" do
+ test "with bad params: doesn't create an Organization" do
assert {:error, %Changeset{}} = Domain.create(%{})
end
end
describe "update/2" do
- test "updates an Organization with valid params", %{params: params, org: old} do
+ test "with good params: updates the Organization", %{params: params, inserted: old} do
assert {:ok, %Organization{} = new} = Domain.update(old.id, params)
-
assert new.name == params.name
assert new.classified_as == params.classified_as
assert new.note == params.note
@@ -90,9 +74,8 @@ describe "update/2" do
assert new.primary_location_id == params.primary_location_id
end
- test "doesn't update an Organization with invalid params", %{org: old} do
+ test "with bad params: doesn't update the Organization", %{inserted: old} do
assert {:ok, %Organization{} = new} = Domain.update(old.id, %{})
-
assert new.name == old.name
assert new.classified_as == old.classified_as
assert new.note == old.note
@@ -101,8 +84,14 @@ describe "update/2" do
end
end
-test "delete/1 deletes an Organization", %{org: %{id: id}} do
- assert {:ok, %Organization{id: ^id}} = Domain.delete(id)
- assert Domain.by_id(id) == nil
+describe "delete/1" do
+ test "with good id: deletes the Organization", %{inserted: %{id: id}} do
+ assert {:ok, %Organization{id: ^id}} = Domain.delete(id)
+ assert {:error, "not found"} = Domain.one(id)
+ end
+
+ test "with bad id: doesn't delete the Organization" do
+ assert {:error, "not found"} = Domain.delete(Factory.id())
+ end
end
end
diff --git a/test/vf/organization/type.test.exs b/test/vf/organization/type.test.exs
@@ -27,32 +27,37 @@ setup do
"note" => Factory.uniq("note"),
"primaryLocation" => Factory.insert!(:spatial_thing).id,
},
- org: Factory.insert!(:organization),
+ inserted: Factory.insert!(:organization),
}
end
+@frag """
+fragment organization on Organization {
+ id
+ name
+ note
+ image
+ primaryLocation { id }
+ classifiedAs
+}
+"""
+
describe "Query" do
- test "organization()", %{org: org} do
+ test "organization()", %{inserted: new} do
assert %{data: %{"organization" => data}} =
run!("""
+ #{@frag}
query ($id: ID!) {
- organization(id: $id) {
- id
- name
- note
- image
- primaryLocation { id }
- classifiedAs
- }
+ organization(id: $id) {...organization}
}
- """, vars: %{"id" => org.id})
+ """, vars: %{"id" => new.id})
- assert data["id"] == org.id
- assert data["name"] == org.name
- assert data["note"] == org.note
- assert data["image"] == org.image
- assert data["primaryLocation"]["id"] == org.primary_location_id
- assert data["classifiedAs"] == org.classified_as
+ assert data["id"] == new.id
+ assert data["name"] == new.name
+ assert data["note"] == new.note
+ assert data["image"] == new.image
+ assert data["primaryLocation"]["id"] == new.primary_location_id
+ assert data["classifiedAs"] == new.classified_as
end
end
@@ -60,16 +65,10 @@ describe "Mutation" do
test "createOrganization", %{params: params} do
assert %{data: %{"createOrganization" => %{"agent" => data}}} =
run!("""
+ #{@frag}
mutation ($organization: OrganizationCreateParams!) {
createOrganization(organization: $organization) {
- agent {
- id
- name
- note
- image
- primaryLocation { id }
- classifiedAs
- }
+ agent {...organization}
}
}
""", vars: %{"organization" => params})
@@ -84,39 +83,34 @@ describe "Mutation" do
assert data == params
end
- test "updateOrganization()", %{params: params, org: org} do
+ test "updateOrganization()", %{params: params, inserted: old} do
assert %{data: %{"updateOrganization" => %{"agent" => data}}} =
run!("""
+ #{@frag}
mutation ($organization: OrganizationUpdateParams!) {
updateOrganization(organization: $organization) {
- agent {
- id
- name
- note
- image
- primaryLocation { id }
- classifiedAs
- }
+ agent {...organization}
}
}
""", vars: %{"organization" =>
params
|> Map.take(~w[name note image primaryLocation classifiedAs])
- |> Map.put("id", org.id)
+ |> Map.put("id", old.id)
})
+ assert data["id"] == old.id
keys = ~w[name image note classifiedAs]
assert Map.take(data, keys) == Map.take(params, keys)
assert data["primaryLocation"]["id"] == params["primaryLocation"]
end
- test "deleteOrganization", %{org: org} do
+ test "deleteOrganization", %{inserted: %{id: id}} do
assert %{data: %{"deleteOrganization" => true}} =
run!("""
mutation ($id: ID!) {
deleteOrganization(id: $id)
}
- """, vars: %{"id" => org.id})
+ """, vars: %{"id" => id})
end
end
end