Fix unlisted groups being available in search

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-12-17 15:25:58 +01:00
parent da564078b3
commit a646d4a40a
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
9 changed files with 68 additions and 26 deletions

View file

@ -172,3 +172,9 @@ export enum RoutingTransportationType {
TRANSIT = "TRANSIT", TRANSIT = "TRANSIT",
CAR = "CAR", CAR = "CAR",
} }
export enum GroupVisibility {
PUBLIC = "PUBLIC",
UNLISTED = "UNLISTED",
PRIVATE = "PRIVATE",
}

View file

@ -80,7 +80,7 @@ import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { CREATE_GROUP } from "@/graphql/group"; import { CREATE_GROUP } from "@/graphql/group";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import IdentityEditionMixin from "@/mixins/identityEdition"; import IdentityEditionMixin from "@/mixins/identityEdition";
import { MemberRole } from "@/types/enums"; import { MemberRole, Openness } from "@/types/enums";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { convertToUsername } from "../../utils/username"; import { convertToUsername } from "../../utils/username";
import PictureUpload from "../../components/PictureUpload.vue"; import PictureUpload from "../../components/PictureUpload.vue";

View file

@ -76,7 +76,7 @@
<b-radio <b-radio
v-model="group.visibility" v-model="group.visibility"
name="groupVisibility" name="groupVisibility"
:native-value="GroupVisibility.UNLISTED" :native-value="GroupVisibility.PRIVATE"
>{{ $t("Only accessible through link") }}<br /> >{{ $t("Only accessible through link") }}<br />
<small>{{ <small>{{
$t( $t(
@ -163,7 +163,7 @@ import { Route } from "vue-router";
import PictureUpload from "@/components/PictureUpload.vue"; import PictureUpload from "@/components/PictureUpload.vue";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import GroupMixin from "@/mixins/group"; import GroupMixin from "@/mixins/group";
import { Openness } from "@/types/enums"; import { GroupVisibility, Openness } from "@/types/enums";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group"; import { UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
import { IGroup, usernameWithDomain } from "../../types/actor"; import { IGroup, usernameWithDomain } from "../../types/actor";
@ -189,10 +189,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
GroupVisibility = { GroupVisibility = GroupVisibility;
PUBLIC: "PUBLIC",
UNLISTED: "UNLISTED",
};
Openness = Openness; Openness = Openness;

View file

@ -40,8 +40,13 @@ defmodule Mobilizon.GraphQL.API.Search do
true -> true ->
page = page =
Actors.build_actors_by_username_or_name_page( Actors.build_actors_by_username_or_name_page(
Map.put(args, :term, term), term,
[result_type], [
actor_type: [result_type],
radius: Map.get(args, :radius),
location: Map.get(args, :location),
minimum_visibility: :public
],
page, page,
limit limit
) )

View file

@ -149,6 +149,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
enum :group_visibility do enum :group_visibility do
value(:public, description: "Publicly listed and federated") value(:public, description: "Publicly listed and federated")
value(:unlisted, description: "Visible only to people with the link - or invited") value(:unlisted, description: "Visible only to people with the link - or invited")
value(:private, description: "Visible only to people with the link - or invited")
end end
object :group_queries do object :group_queries do
@ -198,6 +199,11 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
default_value: :public default_value: :public
) )
arg(:openness, :openness,
default_value: :invite_only,
description: "Whether the group can be join freely, with approval or is invite-only."
)
arg(:avatar, :media_input, arg(:avatar, :media_input,
description: description:
"The avatar for the group, either as an object or directly the ID of an existing media" "The avatar for the group, either as an object or directly the ID of an existing media"

View file

@ -135,7 +135,14 @@ defmodule Mobilizon.Actors.Actor do
:preferred_username, :preferred_username,
:members_url :members_url
] ]
@group_creation_optional_attrs [:shared_inbox_url, :name, :domain, :summary, :visibility] @group_creation_optional_attrs [
:shared_inbox_url,
:name,
:domain,
:summary,
:visibility,
:openness
]
@group_creation_attrs @group_creation_required_attrs ++ @group_creation_optional_attrs @group_creation_attrs @group_creation_required_attrs ++ @group_creation_optional_attrs
schema "actors" do schema "actors" do

View file

@ -512,21 +512,22 @@ defmodule Mobilizon.Actors do
Builds a page struct for actors by their name or displayed name. Builds a page struct for actors by their name or displayed name.
""" """
@spec build_actors_by_username_or_name_page( @spec build_actors_by_username_or_name_page(
map(), String.t(),
[ActorType.t()], Keyword.t(),
integer | nil, integer | nil,
integer | nil integer | nil
) :: Page.t() ) :: Page.t()
def build_actors_by_username_or_name_page( def build_actors_by_username_or_name_page(
%{term: term} = args, term,
types, options \\ [],
page \\ nil, page \\ nil,
limit \\ nil limit \\ nil
) do ) do
Actor Actor
|> actor_by_username_or_name_query(term) |> actor_by_username_or_name_query(term)
|> actors_for_location(args) |> actors_for_location(Keyword.get(options, :location), Keyword.get(options, :radius))
|> filter_by_types(types) |> filter_by_types(Keyword.get(options, :actor_type, :Group))
|> filter_by_minimum_visibility(Keyword.get(options, :minimum_visibility, :public))
|> filter_suspended(false) |> filter_suspended(false)
|> Page.build_page(page, limit) |> Page.build_page(page, limit)
end end
@ -1395,11 +1396,8 @@ defmodule Mobilizon.Actors do
) )
end end
@spec actors_for_location(Ecto.Query.t(), map()) :: Ecto.Query.t() @spec actors_for_location(Ecto.Query.t(), String.t(), integer()) :: Ecto.Query.t()
defp actors_for_location(query, %{radius: radius}) when is_nil(radius), defp actors_for_location(query, location, radius)
do: query
defp actors_for_location(query, %{location: location, radius: radius})
when is_valid_string?(location) and not is_nil(radius) do when is_valid_string?(location) and not is_nil(radius) do
with {lon, lat} <- Geohax.decode(location), with {lon, lat} <- Geohax.decode(location),
point <- Geo.WKT.decode!("SRID=4326;POINT(#{lon} #{lat})") do point <- Geo.WKT.decode!("SRID=4326;POINT(#{lon} #{lat})") do
@ -1414,7 +1412,7 @@ defmodule Mobilizon.Actors do
end end
end end
defp actors_for_location(query, _args), do: query defp actors_for_location(query, _location, _radius), do: query
@spec person_query :: Ecto.Query.t() @spec person_query :: Ecto.Query.t()
defp person_query do defp person_query do
@ -1660,6 +1658,21 @@ defmodule Mobilizon.Actors do
from(a in query, where: a.type in ^types) from(a in query, where: a.type in ^types)
end end
@spec filter_by_minimum_visibility(Ecto.Query.t(), atom()) :: Ecto.Query.t()
defp filter_by_minimum_visibility(query, :private), do: query
defp filter_by_minimum_visibility(query, :restricted) do
from(a in query, where: a.visibility in ^[:public, :unlisted, :restricted])
end
defp filter_by_minimum_visibility(query, :unlisted) do
from(a in query, where: a.visibility in ^[:public, :unlisted])
end
defp filter_by_minimum_visibility(query, :public) do
from(a in query, where: a.visibility == ^:public)
end
@spec filter_by_name(Ecto.Query.t(), [String.t()]) :: Ecto.Query.t() @spec filter_by_name(Ecto.Query.t(), [String.t()]) :: Ecto.Query.t()
defp filter_by_name(query, [name]) do defp filter_by_name(query, [name]) do
from(a in query, where: a.preferred_username == ^name and is_nil(a.domain)) from(a in query, where: a.preferred_username == ^name and is_nil(a.domain))

View file

@ -35,14 +35,19 @@ defmodule Mobilizon.GraphQL.API.SearchTest do
test "search actors" do test "search actors" do
with_mock Actors, with_mock Actors,
build_actors_by_username_or_name_page: fn %{term: "toto"}, _type, 1, 10 -> build_actors_by_username_or_name_page: fn "toto", _options, 1, 10 ->
%Page{total: 1, elements: [%Actor{id: 42}]} %Page{total: 1, elements: [%Actor{id: 42}]}
end do end do
assert {:ok, %{total: 1, elements: [%Actor{id: 42}]}} = assert {:ok, %{total: 1, elements: [%Actor{id: 42}]}} =
Search.search_actors(%{term: "toto"}, 1, 10, :Person) Search.search_actors(%{term: "toto"}, 1, 10, :Person)
assert_called( assert_called(
Actors.build_actors_by_username_or_name_page(%{term: "toto"}, [:Person], 1, 10) Actors.build_actors_by_username_or_name_page(
"toto",
[actor_type: [:Person], radius: nil, location: nil, minimum_visibility: :public],
1,
10
)
) )
end end
end end

View file

@ -188,7 +188,10 @@ defmodule Mobilizon.ActorsTest do
with {:ok, %Actor{id: actor2_id}} <- with {:ok, %Actor{id: actor2_id}} <-
ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do
%Page{total: 2, elements: actors} = %Page{total: 2, elements: actors} =
Actors.build_actors_by_username_or_name_page(%{term: "tcit"}, [:Person]) Actors.build_actors_by_username_or_name_page("tcit",
actor_type: [:Person],
minimum_visibility: :private
)
actors_ids = actors |> Enum.map(& &1.id) actors_ids = actors |> Enum.map(& &1.id)
@ -199,7 +202,7 @@ defmodule Mobilizon.ActorsTest do
test "test build_actors_by_username_or_name_page/4 returns actors with similar names" do test "test build_actors_by_username_or_name_page/4 returns actors with similar names" do
%{total: 0, elements: actors} = %{total: 0, elements: actors} =
Actors.build_actors_by_username_or_name_page(%{term: "ohno"}, [:Person]) Actors.build_actors_by_username_or_name_page("ohno", actor_type: [:Person])
assert actors == [] assert actors == []
end end