Change schema a bit

Closes #29

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2018-11-23 15:03:53 +01:00
parent 403a32e996
commit 9f9113f094
13 changed files with 449 additions and 191 deletions

View file

@ -8,6 +8,12 @@ defenum(Mobilizon.Actors.ActorTypeEnum, :actor_type, [
:Service :Service
]) ])
defenum(Mobilizon.Actors.ActorOpennesssEnum, :openness, [
:invite_only,
:moderated,
:open
])
defmodule Mobilizon.Actors.Actor do defmodule Mobilizon.Actors.Actor do
@moduledoc """ @moduledoc """
Represents an actor (local and remote actors) Represents an actor (local and remote actors)
@ -42,6 +48,7 @@ defmodule Mobilizon.Actors.Actor do
field(:suspended, :boolean, default: false) field(:suspended, :boolean, default: false)
field(:avatar_url, :string) field(:avatar_url, :string)
field(:banner_url, :string) field(:banner_url, :string)
# field(:openness, Mobilizon.Actors.ActorOpennesssEnum, default: :moderated)
many_to_many(:followers, Actor, join_through: Follower) many_to_many(:followers, Actor, join_through: Follower)
has_many(:organized_events, Event, foreign_key: :organizer_actor_id) has_many(:organized_events, Event, foreign_key: :organizer_actor_id)
many_to_many(:memberships, Actor, join_through: Member) many_to_many(:memberships, Actor, join_through: Member)

View file

@ -10,10 +10,12 @@ defmodule Mobilizon.Actors do
alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub
@doc false
def data() do def data() do
Dataloader.Ecto.new(Repo, query: &query/2) Dataloader.Ecto.new(Repo, query: &query/2)
end end
@doc false
def query(queryable, _params) do def query(queryable, _params) do
queryable queryable
end end
@ -312,14 +314,51 @@ defmodule Mobilizon.Actors do
if preload, do: Repo.preload(actor, [:followers]), else: actor if preload, do: Repo.preload(actor, [:followers]), else: actor
end end
def get_actor_by_name(name) do @doc """
case String.split(name, "@") do Get an actor by name
[name] ->
Repo.one(from(a in Actor, where: a.preferred_username == ^name and is_nil(a.domain)))
[name, domain] -> ## Examples
Repo.get_by(Actor, preferred_username: name, domain: domain) iex> get_actor_by_name("tcit")
end %Mobilizon.Actors.Actor{preferred_username: "tcit", domain: nil}
iex> get_actor_by_name("tcit@social.tcit.fr")
%Mobilizon.Actors.Actor{preferred_username: "tcit", domain: "social.tcit.fr"}
iex> get_actor_by_name("tcit", :Group)
nil
"""
@spec get_actor_by_name(String.t(), atom() | nil) :: Actor.t()
def get_actor_by_name(name, type \\ nil) do
# Base query
query = from(a in Actor)
# If we have Person / Group information
query =
if type in [:Person, :Group] do
from(a in query, where: a.type == ^type)
else
query
end
# If the name is a remote actor
query =
case String.split(name, "@") do
[name] -> do_get_actor_by_name(query, name)
[name, domain] -> do_get_actor_by_name(query, name, domain)
end
Repo.one(query)
end
# Get actor by username and domain is nil
defp do_get_actor_by_name(query, name) do
from(a in query, where: a.preferred_username == ^name and is_nil(a.domain))
end
# Get actor by username and domain
defp do_get_actor_by_name(query, name, domain) do
from(a in query, where: a.preferred_username == ^name and a.domain == ^domain)
end end
def get_local_actor_by_name(name) do def get_local_actor_by_name(name) do
@ -331,17 +370,11 @@ defmodule Mobilizon.Actors do
Repo.preload(actor, :organized_events) Repo.preload(actor, :organized_events)
end end
def get_actor_by_name_with_everything(name) do @spec get_actor_by_name_with_everything(String.t(), atom() | nil) :: Actor.t()
actor = def get_actor_by_name_with_everything(name, type \\ nil) do
case String.split(name, "@") do name
[name] -> |> get_actor_by_name(type)
Repo.one(from(a in Actor, where: a.preferred_username == ^name and is_nil(a.domain))) |> Repo.preload(:organized_events)
[name, domain] ->
Repo.one(from(a in Actor, where: a.preferred_username == ^name and a.domain == ^domain))
end
Repo.preload(actor, :organized_events)
end end
def get_or_fetch_by_url(url, preload \\ false) do def get_or_fetch_by_url(url, preload \\ false) do
@ -394,6 +427,7 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Find actors by their name or displayed name Find actors by their name or displayed name
""" """
@spec find_actors_by_username_or_name(String.t(), integer(), integer()) :: list(Actor.t())
def find_actors_by_username_or_name(username, page \\ 1, limit \\ 10) def find_actors_by_username_or_name(username, page \\ 1, limit \\ 10)
def find_actors_by_username_or_name("", _page, _limit), do: [] def find_actors_by_username_or_name("", _page, _limit), do: []
@ -418,6 +452,7 @@ defmodule Mobilizon.Actors do
end end
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
@spec search(String.t()) :: {:ok, list(Actor.t())} | {:ok, []} | {:error, any()}
def search(name) do def search(name) do
# find already saved accounts # find already saved accounts
case find_actors_by_username_or_name(name) do case find_actors_by_username_or_name(name) do

View file

@ -23,7 +23,7 @@ defmodule Mobilizon.Events.Tag.TitleSlug do
nil -> nil ->
slug slug
_story -> _tag ->
slug slug
|> Mobilizon.Slug.increment_slug() |> Mobilizon.Slug.increment_slug()
|> build_unique_slug(changeset) |> build_unique_slug(changeset)
@ -51,8 +51,8 @@ defmodule Mobilizon.Events.Tag do
def changeset(%Tag{} = tag, attrs) do def changeset(%Tag{} = tag, attrs) do
tag tag
|> cast(attrs, [:title]) |> cast(attrs, [:title])
|> validate_required([:title])
|> TitleSlug.maybe_generate_slug() |> TitleSlug.maybe_generate_slug()
|> validate_required([:title, :slug])
|> TitleSlug.unique_constraint() |> TitleSlug.unique_constraint()
end end
end end

View file

@ -12,14 +12,40 @@ defmodule MobilizonWeb.Resolvers.Actor do
end end
end end
@doc """
Find a person
"""
def find_person(_parent, %{preferred_username: name}, _resolution) do
case ActivityPub.find_or_make_person_from_nickname(name) do
{:ok, actor} ->
{:ok, actor}
_ ->
{:error, "Person with name #{name} not found"}
end
end
@doc """
Find a person
"""
def find_group(_parent, %{preferred_username: name}, _resolution) do
case ActivityPub.find_or_make_group_from_nickname(name) do
{:ok, actor} ->
{:ok, actor}
_ ->
{:error, "Group with name #{name} not found"}
end
end
@doc """ @doc """
Returns the current actor for the currently logged-in user Returns the current actor for the currently logged-in user
""" """
def get_current_actor(_parent, _args, %{context: %{current_user: user}}) do def get_current_person(_parent, _args, %{context: %{current_user: user}}) do
{:ok, Actors.get_actor_for_user(user)} {:ok, Actors.get_actor_for_user(user)}
end end
def get_current_actor(_parent, _args, _resolution) do def get_current_person(_parent, _args, _resolution) do
{:error, "You need to be logged-in to view current actor"} {:error, "You need to be logged-in to view current person"}
end end
end end

View file

@ -60,7 +60,7 @@ defmodule MobilizonWeb.Resolvers.User do
Mobilizon.Actors.Service.Activation.check_confirmation_token(token), Mobilizon.Actors.Service.Activation.check_confirmation_token(token),
%Actor{} = actor <- Actors.get_actor_for_user(user), %Actor{} = actor <- Actors.get_actor_for_user(user),
{:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do {:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do
{:ok, %{token: token, user: user, actor: actor}} {:ok, %{token: token, user: user, person: actor}}
end end
end end

View file

@ -3,8 +3,8 @@ defmodule MobilizonWeb.Schema do
import Absinthe.Resolution.Helpers, only: [dataloader: 1] import Absinthe.Resolution.Helpers, only: [dataloader: 1]
alias Mobilizon.{Actors, Events} alias Mobilizon.{Actors, Events}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.{Actor, Follower, Member}
alias Mobilizon.Events.Event alias Mobilizon.Events.{Event, Comment, Participant}
import_types(MobilizonWeb.Schema.Custom.UUID) import_types(MobilizonWeb.Schema.Custom.UUID)
import_types(Absinthe.Type.Custom) import_types(Absinthe.Type.Custom)
@ -12,18 +12,20 @@ defmodule MobilizonWeb.Schema do
alias MobilizonWeb.Resolvers alias MobilizonWeb.Resolvers
@desc "An ActivityPub actor" @desc """
object :actor do Represents a person identity
"""
object :person do
interfaces([:actor])
field(:user, :user, description: "The user this actor is associated to")
field(:member_of, list_of(:member), description: "The list of groups this person is member of")
field(:url, :string, description: "The ActivityPub actor's URL") field(:url, :string, description: "The ActivityPub actor's URL")
# We probably don't need all of that
# field(:outbox_url, :string, description: "The ActivityPub actor outbox_url")
# field(:inbox_url, :string)
# field(:following_url, :string)
# field(:followers_url, :string)
# field(:shared_inbox_url, :string)
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)") field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name") field(:name, :string, description: "The actor's displayed name")
field(:domain, :string, description: "The actor's domain if (null if it's this instance)") field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
field(:local, :boolean, description: "If the actor is from this instance")
field(:summary, :string, description: "The actor's summary") field(:summary, :string, description: "The actor's summary")
field(:preferred_username, :string, description: "The actor's preferred username") field(:preferred_username, :string, description: "The actor's preferred username")
field(:keys, :string, description: "The actors RSA Keys") field(:keys, :string, description: "The actors RSA Keys")
@ -35,14 +37,126 @@ defmodule MobilizonWeb.Schema do
field(:suspended, :boolean, description: "If the actor is suspended") field(:suspended, :boolean, description: "If the actor is suspended")
field(:avatar_url, :string, description: "The actor's avatar url") field(:avatar_url, :string, description: "The actor's avatar url")
field(:banner_url, :string, description: "The actor's banner url") field(:banner_url, :string, description: "The actor's banner url")
# field(:followers, list_of(:follower))
# These one should have a privacy setting
field(:following, list_of(:follower), description: "List of followings")
field(:followers, list_of(:follower), description: "List of followers")
field(:followersCount, :integer, description: "Number of followers for this actor")
field(:followingCount, :integer, description: "Number of actors following this actor")
# This one should have a privacy setting
field(:organized_events, list_of(:event),
resolve: dataloader(Events),
description: "A list of the events this actor has organized"
)
end
@desc """
Represents a group of actors
"""
object :group do
interfaces([:actor])
field(:url, :string, description: "The ActivityPub actor's URL")
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name")
field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
field(:local, :boolean, description: "If the actor is from this instance")
field(:summary, :string, description: "The actor's summary")
field(:preferred_username, :string, description: "The actor's preferred username")
field(:keys, :string, description: "The actors RSA Keys")
field(:manually_approves_followers, :boolean,
description: "Whether the actors manually approves followers"
)
field(:suspended, :boolean, description: "If the actor is suspended")
field(:avatar_url, :string, description: "The actor's avatar url")
field(:banner_url, :string, description: "The actor's banner url")
# These one should have a privacy setting
field(:following, list_of(:follower), description: "List of followings")
field(:followers, list_of(:follower), description: "List of followers")
field(:followersCount, :integer, description: "Number of followers for this actor")
field(:followingCount, :integer, description: "Number of actors following this actor")
# This one should have a privacy setting
field(:organized_events, list_of(:event), field(:organized_events, list_of(:event),
resolve: dataloader(Events), resolve: dataloader(Events),
description: "A list of the events this actor has organized" description: "A list of the events this actor has organized"
) )
field(:types, :group_type, description: "The type of group : Group, Community,…")
field(:openness, :openness,
description: "Whether the group is opened to all or has restricted access"
)
field(:members, non_null(list_of(:member)), description: "List of group members")
end
@desc """
Describes how an actor is opened to follows
"""
enum :openness do
value(:invite_only, description: "The actor can only be followed by invitation")
value(:moderated, description: "The actor needs to accept the following before it's effective")
value(:open, description: "The actor is open to followings")
end
@desc """
The types of Group that exist
"""
enum :group_type do
value(:group, description: "A private group of persons")
value(:community, description: "A public group of many actors")
end
@desc "An ActivityPub actor"
interface :actor do
field(:url, :string, description: "The ActivityPub actor's URL")
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name")
field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
field(:local, :boolean, description: "If the actor is from this instance")
field(:summary, :string, description: "The actor's summary")
field(:preferred_username, :string, description: "The actor's preferred username")
field(:keys, :string, description: "The actors RSA Keys")
field(:manually_approves_followers, :boolean,
description: "Whether the actors manually approves followers"
)
field(:suspended, :boolean, description: "If the actor is suspended")
field(:avatar_url, :string, description: "The actor's avatar url")
field(:banner_url, :string, description: "The actor's banner url")
# These one should have a privacy setting
field(:following, list_of(:follower), description: "List of followings")
field(:followers, list_of(:follower), description: "List of followers")
field(:followersCount, :integer, description: "Number of followers for this actor")
field(:followingCount, :integer, description: "Number of actors following this actor")
# This one should have a privacy setting
field(:organized_events, list_of(:event),
resolve: dataloader(Events),
description: "A list of the events this actor has organized"
)
# This one is for the person itself **only**
# field(:feed, list_of(:event), description: "List of events the actor sees in his or her feed")
# field(:memberships, list_of(:member)) # field(:memberships, list_of(:member))
field(:user, :user, description: "The user this actor is associated to")
resolve_type(fn
%Actor{type: :Person}, _ ->
:person
%Actor{type: :Group}, _ ->
:group
end)
end end
@desc "The list of types an actor can be" @desc "The list of types an actor can be"
@ -58,11 +172,12 @@ defmodule MobilizonWeb.Schema do
object :user do object :user do
field(:id, non_null(:id), description: "The user's ID") field(:id, non_null(:id), description: "The user's ID")
field(:email, non_null(:string), description: "The user's email") field(:email, non_null(:string), description: "The user's email")
# , resolve: dataloader(:actors))
field(:actors, non_null(list_of(:actor)), field(:profiles, non_null(list_of(:person)),
description: "The user's list of actors (identities)" description: "The user's list of profiles (identities)"
) )
# TODO: This shouldn't be an ID, but the actor itself
field(:default_actor_id, non_null(:integer), description: "The user's default actor") field(:default_actor_id, non_null(:integer), description: "The user's default actor")
field(:confirmed_at, :datetime, field(:confirmed_at, :datetime,
@ -86,89 +201,177 @@ defmodule MobilizonWeb.Schema do
@desc "A JWT and the associated user ID" @desc "A JWT and the associated user ID"
object :login do object :login do
field(:token, non_null(:string)) field(:token, non_null(:string), description: "A JWT Token for this session")
field(:user, non_null(:user)) field(:user, non_null(:user), description: "The user associated to this session")
field(:actor, non_null(:actor)) field(:person, non_null(:person), description: "The person associated to this session")
end end
@desc "An event" @desc "An event"
object :event do object :event do
field(:uuid, :uuid) field(:uuid, :uuid, description: "The Event UUID")
field(:url, :string) field(:url, :string, description: "The ActivityPub Event URL")
field(:local, :boolean) field(:local, :boolean, description: "Whether the event is local or not")
field(:title, :string) field(:title, :string, description: "The event's title")
field(:description, :string) field(:description, :string, description: "The event's description")
field(:begins_on, :datetime) field(:begins_on, :datetime, description: "Datetime for when the event begins")
field(:ends_on, :datetime) field(:ends_on, :datetime, description: "Datetime for when the event ends")
field(:state, :integer) field(:state, :integer, description: "State of the event")
field(:status, :integer) field(:status, :integer, description: "Status of the event")
field(:public, :boolean) field(:public, :boolean, description: "Whether the event is public or not")
field(:thumbnail, :string) # TODO replace me with picture object
field(:large_image, :string) field(:thumbnail, :string, description: "A thumbnail picture for the event")
field(:publish_at, :datetime) # TODO replace me with banner
field(:address_type, :address_type) field(:large_image, :string, description: "A large picture for the event")
field(:online_address, :string) field(:publish_at, :datetime, description: "When the event was published")
field(:phone, :string) field(:address_type, :address_type, description: "The type of the event's address")
# TODO implement these properly with an interface
# field(:online_address, :string, description: "???")
# field(:phone, :string, description: "")
field :organizer_actor, :actor do field(:organizer_actor, :person,
resolve(dataloader(Actors)) resolve: dataloader(Actors),
end description: "The event's organizer (as a person)"
)
field(:attributed_to, :actor) field(:attributed_to, :actor, description: "Who the event is attributed to (often a group)")
# field(:tags, list_of(:tag)) # field(:tags, list_of(:tag))
field(:category, :category) field(:category, :category, description: "The event's category")
field(:participants, list_of(:participant), field(:participants, list_of(:participant),
resolve: &Resolvers.Event.list_participants_for_event/3 resolve: &Resolvers.Event.list_participants_for_event/3,
description: "The event's participants"
) )
# field(:tracks, list_of(:track)) # field(:tracks, list_of(:track))
# field(:sessions, list_of(:session)) # field(:sessions, list_of(:session))
# field(:physical_address, :address) # field(:physical_address, :address)
field(:updated_at, :datetime) field(:updated_at, :datetime, description: "When the event was last updated")
field(:created_at, :datetime) field(:created_at, :datetime, description: "When the event was created")
end
@desc "A comment"
object :comment do
field(:uuid, :uuid)
field(:url, :string)
field(:local, :boolean)
field(:content, :string)
field(:primaryLanguage, :string)
field(:replies, list_of(:comment))
field(:threadLanguages, non_null(list_of(:string)))
end end
@desc "Represents a participant to an event" @desc "Represents a participant to an event"
object :participant do object :participant do
# field(:event, :event, resolve: dataloader(Events)) field(:event, :event,
# , resolve: dataloader(Actors) resolve: dataloader(Events),
field(:actor, :actor) description: "The event which the actor participates in"
field(:role, :integer) )
field(:actor, :actor, description: "The actor that participates to the event")
field(:role, :integer, description: "The role of this actor at this event")
end end
@desc "The list of types an address can be" @desc "The list of types an address can be"
enum :address_type do enum :address_type do
value(:physical) value(:physical, description: "The address is physical, like a postal address")
value(:url) value(:url, description: "The address is on the Web, like an URL")
value(:phone) value(:phone, description: "The address is a phone number for a conference")
value(:other) value(:other, description: "The address is something else")
end end
@desc "A category" @desc "A category"
object :category do object :category do
field(:id, :id) field(:id, :id, description: "The category's ID")
field(:description, :string) field(:description, :string, description: "The category's description")
field(:picture, :picture) field(:picture, :picture, description: "The category's picture")
field(:title, :string) field(:title, :string, description: "The category's title")
field(:updated_at, :datetime)
field(:created_at, :datetime)
end end
@desc "A picture" @desc "A picture"
object :picture do object :picture do
field(:url, :string) field(:url, :string, description: "The URL for this picture")
field(:url_thumbnail, :string) field(:url_thumbnail, :string, description: "The URL for this picture's thumbnail")
end
@desc """
Represents a notification for an user
"""
object :notification do
field(:id, :integer, description: "The notification ID")
field(:user, :user, description: "The user to transmit the notification to")
field(:actor, :actor, description: "The notification target profile")
field(:activity_type, :integer,
description:
"Whether the notification is about a follow, group join, event change or comment"
)
field(:target_object, :object, description: "The object responsible for the notification")
field(:summary, :string, description: "Text inside the notification")
field(:seen, :boolean, description: "Whether or not the notification was seen by the user")
field(:published, :datetime, description: "Datetime when the notification was published")
end
@desc """
Represents a member of a group
"""
object :member do
field(:parent, :group, description: "Of which the profile is member")
field(:person, :person, description: "Which profile is member of")
field(:role, :integer, description: "The role of this membership")
field(:approved, :boolean, description: "Whether this membership has been approved")
end
@desc """
Represents an actor's follower
"""
object :follower do
field(:target_actor, :actor, description: "What or who the profile follows")
field(:actor, :actor, description: "Which profile follows")
field(:approved, :boolean,
description: "Whether the follow has been approved by the target actor"
)
end
union :object do
types([:event, :person, :group, :comment, :follower, :member, :participant])
resolve_type(fn
%Actor{type: :Person}, _ ->
:person
%Actor{type: :Group}, _ ->
:group
%Event{}, _ ->
:event
%Comment{}, _ ->
:comment
%Follower{}, _ ->
:follower
%Member{}, _ ->
:member
%Participant{}, _ ->
:participant
end)
end end
@desc "A search result" @desc "A search result"
union :search_result do union :search_result do
types([:event, :actor]) types([:event, :person, :group])
resolve_type(fn resolve_type(fn
%Actor{}, _ -> %Actor{type: :Person}, _ ->
:actor :person
%Actor{type: :Group}, _ ->
:group
%Event{}, _ -> %Event{}, _ ->
:event :event
@ -188,13 +391,16 @@ defmodule MobilizonWeb.Schema do
[Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()] [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
end end
@desc """
Root Query
"""
query do query do
@desc "Get all events" @desc "Get all events"
field :events, list_of(:event) do field :events, list_of(:event) do
resolve(&Resolvers.Event.list_events/3) resolve(&Resolvers.Event.list_events/3)
end end
@desc "Search through events and actors" @desc "Search through events, persons and groups"
field :search, list_of(:search_result) do field :search, list_of(:search_result) do
arg(:search, non_null(:string)) arg(:search, non_null(:string))
arg(:page, :integer, default_value: 1) arg(:page, :integer, default_value: 1)
@ -226,22 +432,25 @@ defmodule MobilizonWeb.Schema do
end end
@desc "Get the current actor for the logged-in user" @desc "Get the current actor for the logged-in user"
field :logged_actor, :actor do field :logged_person, :person do
resolve(&Resolvers.Actor.get_current_actor/3) resolve(&Resolvers.Actor.get_current_person/3)
end end
@desc "Get an actor" @desc "Get a person"
field :actor, :actor do field :person, :person do
arg(:preferred_username, non_null(:string)) arg(:preferred_username, non_null(:string))
resolve(&Resolvers.Actor.find_actor/3) resolve(&Resolvers.Actor.find_person/3)
end end
@desc "Get the list of categories" @desc "Get the list of categories"
field :categories, list_of(:category) do field :categories, non_null(list_of(:category)) do
resolve(&Resolvers.Category.list_categories/3) resolve(&Resolvers.Category.list_categories/3)
end end
end end
@desc """
Root Mutation
"""
mutation do mutation do
@desc "Create an event" @desc "Create an event"
field :create_event, type: :event do field :create_event, type: :event do
@ -273,7 +482,7 @@ defmodule MobilizonWeb.Schema do
end end
@desc "Create an user (returns an actor)" @desc "Create an user (returns an actor)"
field :create_user, type: :actor do field :create_user, type: :person do
arg(:email, non_null(:string)) arg(:email, non_null(:string))
arg(:password, non_null(:string)) arg(:password, non_null(:string))
arg(:username, non_null(:string)) arg(:username, non_null(:string))

View file

@ -219,15 +219,21 @@ defmodule Mobilizon.Service.ActivityPub do
@doc """ @doc """
Find an actor in our local database or call Webfinger to find what's its AP ID is and then fetch it Find an actor in our local database or call Webfinger to find what's its AP ID is and then fetch it
""" """
@spec find_or_make_actor_from_nickname(String.t()) :: tuple() @spec find_or_make_actor_from_nickname(String.t(), atom() | nil) :: tuple()
def find_or_make_actor_from_nickname(nickname) do def find_or_make_actor_from_nickname(nickname, type \\ nil) do
with %Actor{} = actor <- Actors.get_actor_by_name(nickname) do with %Actor{} = actor <- Actors.get_actor_by_name(nickname, type) do
{:ok, actor} {:ok, actor}
else else
nil -> make_actor_from_nickname(nickname) nil -> make_actor_from_nickname(nickname)
end end
end end
@spec find_or_make_person_from_nickname(String.t()) :: tuple()
def find_or_make_person_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Person)
@spec find_or_make_group_from_nickname(String.t()) :: tuple()
def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group)
@doc """ @doc """
Create an actor inside our database from username, using Webfinger to find out it's AP ID and then fetch it Create an actor inside our database from username, using Webfinger to find out it's AP ID and then fetch it
""" """

View file

@ -306,7 +306,7 @@ defmodule Mobilizon.ActorsTest do
test "list_users/0 returns all users" do test "list_users/0 returns all users" do
user = insert(:user) user = insert(:user)
users = Actors.list_users() users = Actors.list_users()
assert users == [user] assert [user.id] == users |> Enum.map(& &1.id)
end end
test "get_user!/1 returns the user with given id" do test "get_user!/1 returns the user with given id" do

View file

@ -48,12 +48,12 @@ defmodule Mobilizon.AddressesTest do
test "list_addresses/0 returns all addresses" do test "list_addresses/0 returns all addresses" do
address = address_fixture() address = address_fixture()
assert Addresses.list_addresses() == [address] assert [address.id] == Addresses.list_addresses() |> Enum.map(& &1.id)
end end
test "get_address!/1 returns the address with given id" do test "get_address!/1 returns the address with given id" do
address = address_fixture() address = address_fixture()
assert Addresses.get_address!(address.id) == address assert Addresses.get_address!(address.id).id == address.id
end end
test "create_address/1 with valid data creates a address" do test "create_address/1 with valid data creates a address" do
@ -69,8 +69,7 @@ defmodule Mobilizon.AddressesTest do
test "update_address/2 with valid data updates the address" do test "update_address/2 with valid data updates the address" do
address = address_fixture() address = address_fixture()
assert {:ok, address} = Addresses.update_address(address, @update_attrs) assert {:ok, %Address{} = address} = Addresses.update_address(address, @update_attrs)
assert %Address{} = address
assert address.addressCountry == "some updated addressCountry" assert address.addressCountry == "some updated addressCountry"
assert address.addressLocality == "some updated addressLocality" assert address.addressLocality == "some updated addressLocality"
assert address.addressRegion == "some updated addressRegion" assert address.addressRegion == "some updated addressRegion"

View file

@ -189,15 +189,15 @@ defmodule Mobilizon.EventsTest do
@invalid_attrs %{description: nil, picture: nil, title: nil} @invalid_attrs %{description: nil, picture: nil, title: nil}
test "list_categories/0 returns all categories", %{category: category} do test "list_categories/0 returns all categories", %{category: category} do
assert Events.list_categories() == [category] assert [category.id] == Events.list_categories() |> Enum.map(& &1.id)
end end
test "get_category!/1 returns the category with given id", %{category: category} do test "get_category!/1 returns the category with given id", %{category: category} do
assert Events.get_category!(category.id) == category assert Events.get_category!(category.id).id == category.id
end end
test "get_category_by_title/1 return the category with given title", %{category: category} do test "get_category_by_title/1 return the category with given title", %{category: category} do
assert Events.get_category_by_title(category.title) == category assert Events.get_category_by_title(category.title).id == category.id
end end
test "create_category/1 with valid data creates a category" do test "create_category/1 with valid data creates a category" do
@ -212,8 +212,7 @@ defmodule Mobilizon.EventsTest do
end end
test "update_category/2 with valid data updates the category", %{category: category} do test "update_category/2 with valid data updates the category", %{category: category} do
assert {:ok, category} = Events.update_category(category, @update_attrs) assert {:ok, %Category{} = category} = Events.update_category(category, @update_attrs)
assert %Category{} = category
assert category.description == "some updated description" assert category.description == "some updated description"
assert category.picture.file_name == @update_attrs.picture.filename assert category.picture.file_name == @update_attrs.picture.filename
assert category.title == "some updated title" assert category.title == "some updated title"
@ -221,7 +220,7 @@ defmodule Mobilizon.EventsTest do
test "update_category/2 with invalid data returns error changeset", %{category: category} do test "update_category/2 with invalid data returns error changeset", %{category: category} do
assert {:error, %Ecto.Changeset{}} = Events.update_category(category, @invalid_attrs) assert {:error, %Ecto.Changeset{}} = Events.update_category(category, @invalid_attrs)
assert category == Events.get_category!(category.id) assert category.description == Events.get_category!(category.id).description
end end
test "delete_category/1 deletes the category", %{category: category} do test "delete_category/1 deletes the category", %{category: category} do
@ -241,28 +240,24 @@ defmodule Mobilizon.EventsTest do
@update_attrs %{title: "some updated title"} @update_attrs %{title: "some updated title"}
@invalid_attrs %{title: nil} @invalid_attrs %{title: nil}
def tag_fixture(attrs \\ %{}) do
{:ok, tag} =
attrs
|> Enum.into(@valid_attrs)
|> Events.create_tag()
tag
end
test "list_tags/0 returns all tags" do test "list_tags/0 returns all tags" do
tag = tag_fixture() tag = insert(:tag)
assert Events.list_tags() == [tag] assert [tag.id] == Events.list_tags() |> Enum.map(& &1.id)
end end
test "get_tag!/1 returns the tag with given id" do test "get_tag!/1 returns the tag with given id" do
tag = tag_fixture() tag = insert(:tag)
assert Events.get_tag!(tag.id) == tag assert Events.get_tag!(tag.id).id == tag.id
end end
test "create_tag/1 with valid data creates a tag" do test "create_tag/1 with valid data creates a tag" do
assert {:ok, %Tag{} = tag} = Events.create_tag(@valid_attrs) assert {:ok, %Tag{} = tag} = Events.create_tag(@valid_attrs)
assert tag.title == "some title" assert tag.title == "some title"
assert tag.slug == "some-title"
assert {:ok, %Tag{} = tag2} = Events.create_tag(@valid_attrs)
assert tag2.title == "some title"
assert tag2.slug == "some-title-1"
end end
test "create_tag/1 with invalid data returns error changeset" do test "create_tag/1 with invalid data returns error changeset" do
@ -270,26 +265,26 @@ defmodule Mobilizon.EventsTest do
end end
test "update_tag/2 with valid data updates the tag" do test "update_tag/2 with valid data updates the tag" do
tag = tag_fixture() tag = insert(:tag)
assert {:ok, tag} = Events.update_tag(tag, @update_attrs) assert {:ok, tag} = Events.update_tag(tag, @update_attrs)
assert %Tag{} = tag assert %Tag{} = tag
assert tag.title == "some updated title" assert tag.title == "some updated title"
end end
test "update_tag/2 with invalid data returns error changeset" do test "update_tag/2 with invalid data returns error changeset" do
tag = tag_fixture() tag = insert(:tag)
assert {:error, %Ecto.Changeset{}} = Events.update_tag(tag, @invalid_attrs) assert {:error, %Ecto.Changeset{}} = Events.update_tag(tag, @invalid_attrs)
assert tag == Events.get_tag!(tag.id) assert tag.id == Events.get_tag!(tag.id).id
end end
test "delete_tag/1 deletes the tag" do test "delete_tag/1 deletes the tag" do
tag = tag_fixture() tag = insert(:tag)
assert {:ok, %Tag{}} = Events.delete_tag(tag) assert {:ok, %Tag{}} = Events.delete_tag(tag)
assert_raise Ecto.NoResultsError, fn -> Events.get_tag!(tag.id) end assert_raise Ecto.NoResultsError, fn -> Events.get_tag!(tag.id) end
end end
test "change_tag/1 returns a tag changeset" do test "change_tag/1 returns a tag changeset" do
tag = tag_fixture() tag = insert(:tag)
assert %Ecto.Changeset{} = Events.change_tag(tag) assert %Ecto.Changeset{} = Events.change_tag(tag)
end end
end end
@ -405,26 +400,14 @@ defmodule Mobilizon.EventsTest do
videos_urls: nil videos_urls: nil
} }
def session_fixture(attrs \\ %{}) do
event = insert(:event)
valid_attrs = Map.put(@valid_attrs, :event_id, event.id)
{:ok, session} =
attrs
|> Enum.into(valid_attrs)
|> Events.create_session()
session
end
test "list_sessions/0 returns all sessions" do test "list_sessions/0 returns all sessions" do
session = session_fixture() session = insert(:session)
assert Events.list_sessions() == [session] assert [session.id] == Events.list_sessions() |> Enum.map(& &1.id)
end end
test "get_session!/1 returns the session with given id" do test "get_session!/1 returns the session with given id" do
session = session_fixture() session = insert(:session)
assert Events.get_session!(session.id) == session assert Events.get_session!(session.id).id == session.id
end end
test "create_session/1 with valid data creates a session" do test "create_session/1 with valid data creates a session" do
@ -446,9 +429,8 @@ defmodule Mobilizon.EventsTest do
end end
test "update_session/2 with valid data updates the session" do test "update_session/2 with valid data updates the session" do
session = session_fixture() session = insert(:session)
assert {:ok, session} = Events.update_session(session, @update_attrs) assert {:ok, %Session{} = session} = Events.update_session(session, @update_attrs)
assert %Session{} = session
assert session.audios_urls == "some updated audios_urls" assert session.audios_urls == "some updated audios_urls"
assert session.language == "some updated language" assert session.language == "some updated language"
assert session.long_abstract == "some updated long_abstract" assert session.long_abstract == "some updated long_abstract"
@ -460,19 +442,19 @@ defmodule Mobilizon.EventsTest do
end end
test "update_session/2 with invalid data returns error changeset" do test "update_session/2 with invalid data returns error changeset" do
session = session_fixture() session = insert(:session)
assert {:error, %Ecto.Changeset{}} = Events.update_session(session, @invalid_attrs) assert {:error, %Ecto.Changeset{}} = Events.update_session(session, @invalid_attrs)
assert session == Events.get_session!(session.id) assert session.title == Events.get_session!(session.id).title
end end
test "delete_session/1 deletes the session" do test "delete_session/1 deletes the session" do
session = session_fixture() session = insert(:session)
assert {:ok, %Session{}} = Events.delete_session(session) assert {:ok, %Session{}} = Events.delete_session(session)
assert_raise Ecto.NoResultsError, fn -> Events.get_session!(session.id) end assert_raise Ecto.NoResultsError, fn -> Events.get_session!(session.id) end
end end
test "change_session/1 returns a session changeset" do test "change_session/1 returns a session changeset" do
session = session_fixture() session = insert(:session)
assert %Ecto.Changeset{} = Events.change_session(session) assert %Ecto.Changeset{} = Events.change_session(session)
end end
end end
@ -488,26 +470,14 @@ defmodule Mobilizon.EventsTest do
} }
@invalid_attrs %{color: nil, description: nil, name: nil} @invalid_attrs %{color: nil, description: nil, name: nil}
def track_fixture(attrs \\ %{}) do
event = insert(:event)
valid_attrs = Map.put(@valid_attrs, :event_id, event.id)
{:ok, track} =
attrs
|> Enum.into(valid_attrs)
|> Events.create_track()
track
end
test "list_tracks/0 returns all tracks" do test "list_tracks/0 returns all tracks" do
track = track_fixture() track = insert(:track)
assert Events.list_tracks() == [track] assert [track.id] == Events.list_tracks() |> Enum.map(& &1.id)
end end
test "get_track!/1 returns the track with given id" do test "get_track!/1 returns the track with given id" do
track = track_fixture() track = insert(:track)
assert Events.get_track!(track.id) == track assert Events.get_track!(track.id).id == track.id
end end
test "create_track/1 with valid data creates a track" do test "create_track/1 with valid data creates a track" do
@ -524,28 +494,27 @@ defmodule Mobilizon.EventsTest do
end end
test "update_track/2 with valid data updates the track" do test "update_track/2 with valid data updates the track" do
track = track_fixture() track = insert(:track)
assert {:ok, track} = Events.update_track(track, @update_attrs) {:ok, %Track{} = track} = Events.update_track(track, @update_attrs)
assert %Track{} = track
assert track.color == "some updated color" assert track.color == "some updated color"
assert track.description == "some updated description" assert track.description == "some updated description"
assert track.name == "some updated name" assert track.name == "some updated name"
end end
test "update_track/2 with invalid data returns error changeset" do test "update_track/2 with invalid data returns error changeset" do
track = track_fixture() track = insert(:track)
assert {:error, %Ecto.Changeset{}} = Events.update_track(track, @invalid_attrs) assert {:error, %Ecto.Changeset{}} = Events.update_track(track, @invalid_attrs)
assert track == Events.get_track!(track.id) assert track.color == Events.get_track!(track.id).color
end end
test "delete_track/1 deletes the track" do test "delete_track/1 deletes the track" do
track = track_fixture() track = insert(:track)
assert {:ok, %Track{}} = Events.delete_track(track) assert {:ok, %Track{}} = Events.delete_track(track)
assert_raise Ecto.NoResultsError, fn -> Events.get_track!(track.id) end assert_raise Ecto.NoResultsError, fn -> Events.get_track!(track.id) end
end end
test "change_track/1 returns a track changeset" do test "change_track/1 returns a track changeset" do
track = track_fixture() track = insert(:track)
assert %Ecto.Changeset{} = Events.change_track(track) assert %Ecto.Changeset{} = Events.change_track(track)
end end
end end

View file

@ -1,4 +1,4 @@
defmodule MobilizonWeb.Resolvers.ActorResolverTest do defmodule MobilizonWeb.Resolvers.PersonResolverTest do
use MobilizonWeb.ConnCase use MobilizonWeb.ConnCase
alias Mobilizon.Actors alias Mobilizon.Actors
alias MobilizonWeb.AbsintheHelpers alias MobilizonWeb.AbsintheHelpers
@ -6,13 +6,13 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do
@valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"} @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"}
@non_existent_username "nonexistent" @non_existent_username "nonexistent"
describe "Actor Resolver" do describe "Person Resolver" do
test "find_actor/3 returns an actor by it's username", context do test "find_actor/3 returns a person by it's username", context do
{:ok, actor} = Actors.register(@valid_actor_params) {:ok, actor} = Actors.register(@valid_actor_params)
query = """ query = """
{ {
actor(preferredUsername: "#{actor.preferred_username}") { person(preferredUsername: "#{actor.preferred_username}") {
preferredUsername, preferredUsername,
} }
} }
@ -20,14 +20,14 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do
res = res =
context.conn context.conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "actor")) |> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
assert json_response(res, 200)["data"]["actor"]["preferredUsername"] == assert json_response(res, 200)["data"]["person"]["preferredUsername"] ==
actor.preferred_username actor.preferred_username
query = """ query = """
{ {
actor(preferredUsername: "#{@non_existent_username}") { person(preferredUsername: "#{@non_existent_username}") {
preferredUsername, preferredUsername,
} }
} }
@ -35,20 +35,20 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do
res = res =
context.conn context.conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "actor")) |> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
assert json_response(res, 200)["data"]["actor"] == nil assert json_response(res, 200)["data"]["person"] == nil
assert hd(json_response(res, 200)["errors"])["message"] == assert hd(json_response(res, 200)["errors"])["message"] ==
"Actor with name #{@non_existent_username} not found" "Person with name #{@non_existent_username} not found"
end end
test "get_current_actor/3 returns the current logged-in actor", context do test "get_current_person/3 returns the current logged-in actor", context do
{:ok, actor} = Actors.register(@valid_actor_params) {:ok, actor} = Actors.register(@valid_actor_params)
query = """ query = """
{ {
loggedActor { loggedPerson {
avatarUrl, avatarUrl,
preferredUsername, preferredUsername,
} }
@ -57,19 +57,19 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do
res = res =
context.conn context.conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "logged_actor")) |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
assert json_response(res, 200)["data"]["loggedActor"] == nil assert json_response(res, 200)["data"]["loggedPerson"] == nil
assert hd(json_response(res, 200)["errors"])["message"] == assert hd(json_response(res, 200)["errors"])["message"] ==
"You need to be logged-in to view current actor" "You need to be logged-in to view current person"
res = res =
context.conn context.conn
|> auth_conn(actor.user) |> auth_conn(actor.user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "logged_actor")) |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
assert json_response(res, 200)["data"]["loggedActor"]["preferredUsername"] == assert json_response(res, 200)["data"]["loggedPerson"]["preferredUsername"] ==
actor.preferred_username actor.preferred_username
end end
end end

View file

@ -140,7 +140,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
user { user {
id id
}, },
actor { person {
preferredUsername preferredUsername
} }
} }
@ -151,7 +151,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
context.conn context.conn
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["data"]["validateUser"]["actor"]["preferredUsername"] == assert json_response(res, 200)["data"]["validateUser"]["person"]["preferredUsername"] ==
@valid_actor_params.username @valid_actor_params.username
assert json_response(res, 200)["data"]["validateUser"]["user"]["id"] == assert json_response(res, 200)["data"]["validateUser"]["user"]["id"] ==
@ -170,7 +170,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
user { user {
id id
}, },
actor { person {
preferredUsername preferredUsername
} }
} }

View file

@ -53,6 +53,13 @@ defmodule Mobilizon.Factory do
} }
end end
def tag_factory do
%Mobilizon.Events.Tag{
title: "MyTag",
slug: sequence("MyTag")
}
end
def address_factory do def address_factory do
%Mobilizon.Addresses.Address{ %Mobilizon.Addresses.Address{
description: sequence("MyAddress"), description: sequence("MyAddress"),