zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

commit 4ff13842464691001fbe98354b23828108efabc7
parent 1d9bb41b9792f9c912799bfb3d2c931acd8bae55
Author: sir fish <dev@srf.sh>
Date:   Fri, 23 Sep 2022 14:00:52 +0000

Merge pull request #18 from dyne/srfsh/filter

Add more filters and improve filtering
Diffstat:
Asrc/zenflows/db/filter.ex | 28++++++++++++++++++++++++++++
Msrc/zenflows/db/paging.ex | 3+--
Msrc/zenflows/vf/agent/domain.ex | 10++++++----
Asrc/zenflows/vf/agent/filter.ex | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/vf/agent/type.ex | 5+++++
Msrc/zenflows/vf/economic_resource/domain.ex | 20++++++--------------
Asrc/zenflows/vf/economic_resource/filter.ex | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/vf/organization/domain.ex | 12++++++------
Asrc/zenflows/vf/organization/filter.ex | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/vf/organization/type.ex | 5+++++
Msrc/zenflows/vf/person/domain.ex | 10++++++----
Asrc/zenflows/vf/person/filter.ex | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/vf/person/type.ex | 7+++++++
Msrc/zenflows/vf/proposal/domain.ex | 10++++++----
Asrc/zenflows/vf/proposal/filter.ex | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/zenflows/vf/proposal/type.ex | 6++++++
16 files changed, 431 insertions(+), 34 deletions(-)

diff --git a/src/zenflows/db/filter.ex b/src/zenflows/db/filter.ex @@ -0,0 +1,28 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.DB.Filter do +@moduledoc "Filtering helpers for Filter modules." + +@type params() :: %{atom() => term()} +@type error() :: {:error, Changeset.t()} +@type result() :: {:ok, Query.t()} | error() + +def escape_like(v) do + Regex.replace(~r/\\|%|_/, v, &"\\#{&1}") +end +end diff --git a/src/zenflows/db/paging.ex b/src/zenflows/db/paging.ex @@ -43,8 +43,7 @@ alias Zenflows.DB.{ID, Repo} node: struct(), } -@type params() :: %{first: non_neg_integer(), after: ID.t()} - | %{last: non_neg_integer, before: ID.t()} +@type params() :: %{atom() => term()} @spec def_page_size() :: non_neg_integer() def def_page_size() do diff --git a/src/zenflows/vf/agent/domain.ex b/src/zenflows/vf/agent/domain.ex @@ -19,7 +19,7 @@ defmodule Zenflows.VF.Agent.Domain do @moduledoc "Domain logic of Agents." alias Zenflows.DB.{Paging, Repo} -alias Zenflows.VF.Agent +alias Zenflows.VF.{Agent, Agent.Filter} @typep repo() :: Ecto.Repo.t() @typep id() :: Zenflows.DB.Schema.id() @@ -34,9 +34,11 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Agent, params) +@spec all(Paging.params()) :: Filter.error() | Paging.result() +def all(params \\ %{}) do + with {:ok, q} <- Filter.filter(params[:filter] || %{}) do + Paging.page(q, params) + end end @spec preload(Agent.t(), :images | :primary_location) :: Agent.t() diff --git a/src/zenflows/vf/agent/filter.ex b/src/zenflows/vf/agent/filter.ex @@ -0,0 +1,55 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.VF.Agent.Filter do +@moduledoc "Filtering logic of Agents." + +use Zenflows.DB.Schema + +import Ecto.Query + +alias Ecto.Query +alias Zenflows.DB.Filter +alias Zenflows.VF.{Agent, Validate} + +@type error() :: Filter.error() + +@spec filter(Filter.params()) :: Filter.result() +def filter(params) do + case chgset(params) do + %{valid?: true, changes: c} -> + {:ok, Enum.reduce(c, Agent, &f(&2, &1))} + %{valid?: false} = cset -> + {:error, cset} + end +end + +@spec f(Query.t(), {atom(), term()}) :: Query.t() +defp f(q, {:name, v}), + do: where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) + +embedded_schema do + field :name, :string +end + +@spec chgset(params()) :: Changeset.t() +defp chgset(params) do + %__MODULE__{} + |> Changeset.cast(params, [:name]) + |> Validate.name(:name) +end +end diff --git a/src/zenflows/vf/agent/type.ex b/src/zenflows/vf/agent/type.ex @@ -70,6 +70,10 @@ object :agent_connection do field :edges, non_null(list_of(non_null(:agent_edge))) end +input_object :agent_filter_params do + field :name, :string +end + object :query_agent do @desc "Loads details of the currently authenticated agent." field :my_agent, :agent do @@ -91,6 +95,7 @@ object :query_agent do arg :after, :id arg :last, :integer arg :before, :id + arg :filter, :agent_filter_params resolve &Resolv.agents/2 end end diff --git a/src/zenflows/vf/economic_resource/domain.ex b/src/zenflows/vf/economic_resource/domain.ex @@ -18,13 +18,12 @@ defmodule Zenflows.VF.EconomicResource.Domain do @moduledoc "Domain logic of EconomicResources." -import Ecto.Query - alias Ecto.Multi alias Zenflows.DB.{Paging, Repo} alias Zenflows.VF.{ Action, EconomicResource, + EconomicResource.Filter, Measure, } @@ -44,20 +43,13 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(filter(params[:filter]), params) -end - -defp filter(params) do - Enum.reduce(params || %{}, EconomicResource, &filt(&2, &1)) +@spec all(Paging.params()) :: Filter.error() | Paging.result() +def all(params \\ %{}) do + with {:ok, q} <- Filter.filter(params[:filter] || %{}) do + Paging.page(q, params) + end end -defp filt(q, {:classified_as, v}), do: where(q, [x], fragment("? @> ?", x.classified_as, ^v)) -defp filt(q, {:primary_accountable, v}), do: where(q, [x], x.primary_accountable_id in ^v) -defp filt(q, {:custodian, v}), do: where(q, [x], x.custodian_id in ^v) -defp filt(q, {:conforms_to, v}), do: where(q, [x], x.conforms_to_id in ^v) - @spec update(id(), params()) :: {:ok, EconomicResource.t()} | {:error, error()} def update(id, params) do Multi.new() diff --git a/src/zenflows/vf/economic_resource/filter.ex b/src/zenflows/vf/economic_resource/filter.ex @@ -0,0 +1,69 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.VF.EconomicResource.Filter do +@moduledoc "Filtering logic of EconomicResources." + +use Zenflows.DB.Schema + +import Ecto.Query + +alias Ecto.Query +alias Zenflows.DB.{Filter, ID} +alias Zenflows.VF.{EconomicResource, Validate} + +@type error() :: Filter.error() + +@spec filter(Filter.params()) :: Filter.result() +def filter(params) do + case chgset(params) do + %{valid?: true, changes: c} -> + {:ok, Enum.reduce(c, EconomicResource, &f(&2, &1))} + %{valid?: false} = cset -> + {:error, cset} + end +end + +@spec f(Query.t(), {atom(), term()}) :: Query.t() +defp f(q, {:classified_as, v}), + do: where(q, [x], fragment("? @> ?", x.classified_as, ^v)) +defp f(q, {:primary_accountable, v}), + do: where(q, [x], x.primary_accountable_id in ^v) +defp f(q, {:custodian, v}), + do: where(q, [x], x.custodian_id in ^v) +defp f(q, {:conforms_to, v}), + do: where(q, [x], x.conforms_to_id in ^v) + +embedded_schema do + field :classified_as, {:array, :string} + field :primary_accountable, {:array, ID} + field :custodian, {:array, ID} + field :conforms_to, {:array, ID} +end + +@cast ~w[classified_as primary_accountable custodian conforms_to]a + +@spec chgset(params()) :: Changeset.t() +defp chgset(params) do + %__MODULE__{} + |> Changeset.cast(params, @cast) + |> Validate.class(:classified_as) + |> Validate.class(:primary_accountable) + |> Validate.class(:custodian) + |> Validate.class(:conforms_to) +end +end diff --git a/src/zenflows/vf/organization/domain.ex b/src/zenflows/vf/organization/domain.ex @@ -18,11 +18,9 @@ defmodule Zenflows.VF.Organization.Domain do @moduledoc "Domain logic of Organizations." -import Ecto.Query - alias Ecto.Multi alias Zenflows.DB.{Paging, Repo} -alias Zenflows.VF.Organization +alias Zenflows.VF.{Organization, Organization.Filter} @typep repo() :: Ecto.Repo.t() @typep chgset() :: Ecto.Changeset.t() @@ -43,9 +41,11 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(where(Organization, type: :org), params) +@spec all(Paging.params()) :: Filter.error() | Paging.result() +def all(params \\ %{}) do + with {:ok, q} <- Filter.filter(params[:filter] || %{}) do + Paging.page(q, params) + end end @spec create(params()) :: {:ok, Organization.t()} | {:error, chgset()} diff --git a/src/zenflows/vf/organization/filter.ex b/src/zenflows/vf/organization/filter.ex @@ -0,0 +1,55 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.VF.Organization.Filter do +@moduledoc "Filtering logic of Organizations." + +use Zenflows.DB.Schema + +import Ecto.Query + +alias Ecto.Query +alias Zenflows.DB.Filter +alias Zenflows.VF.{Organization, Validate} + +@type error() :: Filter.error() + +@spec filter(Filter.params()) :: Filter.result() +def filter(params) do + case chgset(params) do + %{valid?: true, changes: c} -> + {:ok, Enum.reduce(c, where(Organization, type: :org), &f(&2, &1))} + %{valid?: false} = cset -> + {:error, cset} + end +end + +@spec f(Query.t(), {atom(), term()}) :: Query.t() +defp f(q, {:name, v}), + do: where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) + +embedded_schema do + field :name, :string +end + +@spec chgset(params()) :: Changeset.t() +defp chgset(params) do + %__MODULE__{} + |> Changeset.cast(params, [:name]) + |> Validate.name(:name) +end +end diff --git a/src/zenflows/vf/organization/type.ex b/src/zenflows/vf/organization/type.ex @@ -109,6 +109,10 @@ object :organization_connection do field :edges, non_null(list_of(non_null(:organization_edge))) end +input_object :organization_filter_params do + field :name, :string +end + object :query_organization do @desc "Find an organization (group) agent by its ID." field :organization, :organization do @@ -125,6 +129,7 @@ object :query_organization do arg :after, :id arg :last, :integer arg :before, :id + arg :filter, :organization_filter_params resolve &Resolv.organizations/2 end end diff --git a/src/zenflows/vf/person/domain.ex b/src/zenflows/vf/person/domain.ex @@ -22,7 +22,7 @@ import Ecto.Query alias Ecto.Multi alias Zenflows.DB.{Paging, Repo} -alias Zenflows.VF.Person +alias Zenflows.VF.{Person, Person.Filter} @typep repo() :: Ecto.Repo.t() @typep chgset() :: Ecto.Changeset.t() @@ -42,9 +42,11 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(where(Person, type: :per), params) +@spec all(Paging.params()) :: Filter.error() | Paging.result() +def all(params \\ %{}) do + with {:ok, q} <- Filter.filter(params[:filter] || %{}) do + Paging.page(q, params) + end end @spec exists?(Keyword.t()) :: boolean() diff --git a/src/zenflows/vf/person/filter.ex b/src/zenflows/vf/person/filter.ex @@ -0,0 +1,92 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.VF.Person.Filter do +@moduledoc "Filtering logic of Persons." + +use Zenflows.DB.Schema + +import Ecto.Query + +alias Ecto.Query +alias Zenflows.DB.Filter +alias Zenflows.VF.{Person, Validate} + +@type error() :: Filter.error() + +@spec filter(Filter.params()) :: Filter.result() +def filter(params) do + case chgset(params) do + %{valid?: true, changes: c} -> + {:ok, Enum.reduce(c, where(Person, type: :per), &f(&2, &1))} + %{valid?: false} = cset -> + {:error, cset} + end +end + +@spec f(Query.t(), {atom(), term()}) :: Query.t() +defp f(q, {:name, v}), + do: where(q, [x], ilike(x.name, ^"%#{Filter.escape_like(v)}%")) +defp f(q, {:user, v}), + do: where(q, [x], ilike(x.user, ^"%#{Filter.escape_like(v)}%")) +defp f(q, {:user_or_name, v}) do + v = "%#{Filter.escape_like(v)}%" + where(q, [x], ilike(x.user, ^v) or ilike(x.name, ^v)) +end + +embedded_schema do + field :name, :string + field :user, :string + field :user_or_name, :string +end + +@spec chgset(params()) :: Changeset.t() +defp chgset(params) do + %__MODULE__{} + |> Changeset.cast(params, ~w[name user user_or_name]a) + |> Validate.name(:name) + |> Validate.name(:user) + |> Validate.name(:user_or_name) + |> user_or_name_mutex() +end + +# Validate that `user_or_name` is mutually exclusive with either `user` +# or `name`. +@spec user_or_name_mutex(Changeset.t()) :: Changeset.t() +defp user_or_name_mutex(cset) do + name = Changeset.get_change(cset, :name) + user = Changeset.get_change(cset, :user) + user_or_name = Changeset.get_change(cset, :user_or_name) + + cond do + user_or_name && user -> + msg = "user-or-name and user can't be used together" + cset + |> Changeset.add_error(:user_or_name, msg) + |> Changeset.add_error(:user, msg) + + user_or_name && name -> + msg = "user-or-name and name can't be used together" + cset + |> Changeset.add_error(:user_or_name, msg) + |> Changeset.add_error(:name, msg) + + true -> + cset + end +end +end diff --git a/src/zenflows/vf/person/type.ex b/src/zenflows/vf/person/type.ex @@ -148,6 +148,12 @@ object :person_connection do field :edges, non_null(list_of(non_null(:person_edge))) end +input_object :person_filter_params do + field :name, :string + field :user, :string + field :user_or_name, :string +end + object :query_person do @desc "Find a person by their ID." field :person, :person do @@ -164,6 +170,7 @@ object :query_person do arg :after, :id arg :last, :integer arg :before, :id + arg :filter, :person_filter_params resolve &Resolv.people/2 end diff --git a/src/zenflows/vf/proposal/domain.ex b/src/zenflows/vf/proposal/domain.ex @@ -20,7 +20,7 @@ defmodule Zenflows.VF.Proposal.Domain do alias Ecto.Multi alias Zenflows.DB.{Paging, Repo} -alias Zenflows.VF.Proposal +alias Zenflows.VF.{Proposal, Proposal.Filter} @typep repo() :: Ecto.Repo.t() @typep chgset() :: Ecto.Changeset.t() @@ -38,9 +38,11 @@ def one(repo, clauses) do end end -@spec all(Paging.params()) :: Paging.result() -def all(params) do - Paging.page(Proposal, params) +@spec all(Paging.params()) :: Filter.error() | Paging.result() +def all(params \\ %{}) do + with {:ok, q} <- Filter.filter(params[:filter] || %{}) do + Paging.page(q, params) + end end @spec create(params()) :: {:ok, Proposal.t()} | {:error, chgset()} diff --git a/src/zenflows/vf/proposal/filter.ex b/src/zenflows/vf/proposal/filter.ex @@ -0,0 +1,78 @@ +# Zenflows is designed to implement the Valueflows vocabulary, +# written and maintained by srfsh <info@dyne.org>. +# Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +defmodule Zenflows.VF.Proposal.Filter do +@moduledoc "Filtering logic of Proposals." + +use Zenflows.DB.Schema + +import Ecto.Query + +alias Ecto.Query +alias Zenflows.DB.{Filter, ID} +alias Zenflows.VF.{Proposal, Validate} + +@type error() :: Filter.error() + +@spec filter(Filter.params()) :: Filter.result() +def filter(params) do + case chgset(params) do + %{valid?: true, changes: c} -> + {:ok, Enum.reduce(c, Proposal, &f(&2, &1))} + %{valid?: false} = cset -> + {:error, cset} + end +end + +@spec f(Query.t(), {atom(), term()}) :: Query.t() +defp f(q, {:primary_intents_resource_inventoried_as_conforms_to, v}) do + q = if has_named_binding?(q, :pi), + do: q, + else: join(q, :inner, [x], pi in assoc(x, :primary_intents), as: :pi) + q = if has_named_binding?(q, :r), + do: q, + else: join(q, :inner, [pi: pi], r in assoc(pi, :resource_inventoried_as), as: :r) + where(q, [r: r], r.conforms_to_id in ^v) +end +defp f(q, {:primary_intents_resource_inventoried_as_primary_accountable, v}) do + q = if has_named_binding?(q, :pi), + do: q, + else: join(q, :inner, [x], pi in assoc(x, :primary_intents), as: :pi) + q = if has_named_binding?(q, :r), + do: q, + else: join(q, :inner, [pi: pi], r in assoc(pi, :resource_inventoried_as), as: :r) + where(q, [r: r], r.primary_accountable_id in ^v) +end + +embedded_schema do + field :primary_intents_resource_inventoried_as_conforms_to, {:array, ID} + field :primary_intents_resource_inventoried_as_primary_accountable, {:array, ID} +end + +@cast ~w[ + primary_intents_resource_inventoried_as_conforms_to + primary_intents_resource_inventoried_as_primary_accountable +]a + +@spec chgset(params()) :: Changeset.t() +defp chgset(params) do + %__MODULE__{} + |> Changeset.cast(params, @cast) + |> Validate.class(:primary_intents_resource_inventoried_as_conforms_to) + |> Validate.class(:primary_intents_resource_inventoried_as_primary_accountable) +end +end diff --git a/src/zenflows/vf/proposal/type.ex b/src/zenflows/vf/proposal/type.ex @@ -130,6 +130,11 @@ object :proposal_connection do field :edges, non_null(list_of(non_null(:proposal_edge))) end +input_object :proposal_filter_params do + field :primary_intents_resource_inventoried_as_conforms_to, list_of(non_null(:id)) + field :primary_intents_resource_inventoried_as_primary_accountable, list_of(non_null(:id)) +end + object :query_proposal do field :proposal, :proposal do arg :id, non_null(:id) @@ -142,6 +147,7 @@ object :query_proposal do arg :after, :id arg :last, :integer arg :before, :id + arg :filter, :proposal_filter_params resolve &Resolv.proposals/2 end