diff --git a/config/config.exs b/config/config.exs index be9dc4e7a..deb39dbe7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -15,7 +15,8 @@ config :eventos, :instance, registrations_open: true config :mime, :types, %{ - "application/activity+json" => ["activity-json"] + "application/activity+json" => ["activity-json"], + "application/jrd+json" => ["jrd-json"] } # Configures the endpoint diff --git a/lib/eventos/actors/actor.ex b/lib/eventos/actors/actor.ex new file mode 100644 index 000000000..3d7d0fc09 --- /dev/null +++ b/lib/eventos/actors/actor.ex @@ -0,0 +1,167 @@ +defmodule Eventos.Actors.Actor.TitleSlug do + @moduledoc """ + Slug generation for groups + """ + alias Eventos.Actors.Actor + import Ecto.Query + alias Eventos.Repo + use EctoAutoslugField.Slug, from: :title, to: :slug + + def build_slug(sources, changeset) do + slug = super(sources, changeset) + build_unique_slug(slug, changeset) + end + + defp build_unique_slug(slug, changeset) do + query = from a in Actor, + where: a.slug == ^slug + + case Repo.one(query) do + nil -> slug + _story -> + slug + |> Eventos.Slug.increment_slug() + |> build_unique_slug(changeset) + end + end +end + +import EctoEnum +defenum Eventos.Actors.ActorTypeEnum, :actor_type, [:Person, :Application, :Group, :Organization, :Service] + + +defmodule Eventos.Actors.Actor do + @moduledoc """ + Represents an actor (local and remote actors) + """ + use Ecto.Schema + import Ecto.Changeset + alias Eventos.Actors + alias Eventos.Actors.{Actor, User, Follower, Member} + alias Eventos.Events.Event + alias Eventos.Service.ActivityPub + + import Ecto.Query + alias Eventos.Repo + + import Logger + +# @type t :: %Actor{description: String.t, id: integer(), inserted_at: DateTime.t, updated_at: DateTime.t, display_name: String.t, domain: String.t, private_key: String.t, public_key: String.t, suspended: boolean(), url: String.t, username: String.t, organized_events: list(), groups: list(), group_request: list(), user: User.t, field: ActorTypeEnum.t} + + schema "actors" do + field :url, :string + field :outbox_url, :string + field :inbox_url, :string + field :following_url, :string + field :followers_url, :string + field :shared_inbox_url, :string + field :type, Eventos.Actors.ActorTypeEnum + field :name, :string + field :domain, :string + field :summary, :string + field :preferred_username, :string + field :public_key, :string + field :private_key, :string + field :manually_approves_followers, :boolean + field :suspended, :boolean, default: false + many_to_many :followers, Actor, join_through: Follower + has_many :organized_events, Event, [foreign_key: :organizer_actor_id] + many_to_many :memberships, Actor, join_through: Member + has_one :user, User + + timestamps() + end + + @doc false + def changeset(%Actor{} = actor, attrs) do + actor + |> Ecto.Changeset.cast(attrs, [:url, :outbox_url, :inbox_url, :following_url, :followers_url, :type, :name, :domain, :summary, :preferred_username, :public_key, :private_key, :manually_approves_followers, :suspended]) + |> validate_required([:preferred_username, :public_key, :suspended, :url]) + |> unique_constraint(:name, name: :actors_username_domain_index) + end + + def registration_changeset(%Actor{} = actor, attrs) do + actor + |> Ecto.Changeset.cast(attrs, [:name, :domain, :display_name, :description, :private_key, :public_key, :suspended, :url]) + |> validate_required([:preferred_username, :public_key, :suspended, :url]) + |> unique_constraint(:name) + 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])?)*$/ + def remote_actor_creation(params) do + changes = + %Actor{} + |> Ecto.Changeset.cast(params, [:url, :outbox_url, :inbox_url, :following_url, :followers_url, :type, :name, :domain, :summary, :preferred_username, :public_key, :manually_approves_followers]) + |> validate_required([:url, :outbox_url, :inbox_url, :following_url, :followers_url, :type, :name, :domain, :summary, :preferred_username, :public_key, :manually_approves_followers]) + |> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_index) + |> validate_length(:summary, max: 5000) + |> validate_length(:preferred_username, max: 100) + |> put_change(:local, false) + + Logger.debug("Remote actor creation") + Logger.debug(inspect changes) + changes + # if changes.valid? do + # case changes.changes[:info]["source_data"] do + # %{"followers" => followers} -> + # changes + # |> put_change(:follower_address, followers) + # + # _ -> + # followers = User.ap_followers(%User{nickname: changes.changes[:nickname]}) + # + # changes + # |> put_change(:follower_address, followers) + # end + # else + # changes + # end + end + + def get_or_fetch_by_url(url) do + if user = Actors.get_actor_by_url(url) do + user + else + case ActivityPub.make_actor_from_url(url) do + {:ok, user} -> + user + _ -> {:error, "Could not fetch by AP id"} + end + end + end + + #@spec get_public_key_for_url(Actor.t) :: {:ok, String.t} + def get_public_key_for_url(url) do + with %Actor{} = actor <- get_or_fetch_by_url(url) do + get_public_key_for_actor(actor) + else + _ -> :error + end + end + + #@spec get_public_key_for_actor(Actor.t) :: {:ok, String.t} + def get_public_key_for_actor(%Actor{} = actor) do + {:ok, actor.public_key} + end + + #@spec get_private_key_for_actor(Actor.t) :: {:ok, String.t} + def get_private_key_for_actor(%Actor{} = actor) do + actor.private_key + end + + def get_followers(%Actor{id: actor_id} = actor) do + Repo.all( + from a in Actor, + join: f in Follower, on: a.id == f.actor_id, + where: f.target_actor_id == ^actor_id + ) + end + + def get_followings(%Actor{id: actor_id} = actor) do + Repo.all( + from a in Actor, + join: f in Follower, on: a.id == f.target_actor_id, + where: f.actor_id == ^actor_id + ) + end +end diff --git a/lib/eventos/actors/actors.ex b/lib/eventos/actors/actors.ex new file mode 100644 index 000000000..0b442428a --- /dev/null +++ b/lib/eventos/actors/actors.ex @@ -0,0 +1,444 @@ +defmodule Eventos.Actors do + @moduledoc """ + The Actors context. + """ + + import Ecto.Query, warn: false + alias Eventos.Repo + + alias Eventos.Actors.Actor + alias Eventos.Actors + + alias Eventos.Service.ActivityPub + + @doc """ + Returns the list of actors. + + ## Examples + + iex> list_actors() + [%Actor{}, ...] + + """ + def list_actors do + Repo.all(Actor) + end + + @doc """ + Gets a single actor. + + Raises `Ecto.NoResultsError` if the Actor does not exist. + + ## Examples + + iex> get_actor!(123) + %Actor{} + + iex> get_actor!(456) + ** (Ecto.NoResultsError) + + """ + def get_actor!(id) do + Repo.get!(Actor, id) + end + + def get_actor_with_everything!(id) do + actor = Repo.get!(Actor, id) + Repo.preload(actor, :organized_events) + end + + @doc """ + Creates a actor. + + ## Examples + + iex> create_actor(%{field: value}) + {:ok, %Actor{}} + + iex> create_actor(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_actor(attrs \\ %{}) do + %Actor{} + |> Actor.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a actor. + + ## Examples + + iex> update_actor(actor, %{field: new_value}) + {:ok, %Actor{}} + + iex> update_actor(actor, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_actor(%Actor{} = actor, attrs) do + actor + |> Actor.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Actor. + + ## Examples + + iex> delete_actor(actor) + {:ok, %Actor{}} + + iex> delete_actor(actor) + {:error, %Ecto.Changeset{}} + + """ + def delete_actor(%Actor{} = actor) do + Repo.delete(actor) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking actor changes. + + ## Examples + + iex> change_actor(actor) + %Ecto.Changeset{source: %Actor{}} + + """ + def change_actor(%Actor{} = actor) do + Actor.changeset(actor, %{}) + end + + @doc """ + Returns a text representation of a local actor like user@domain.tld + """ + def actor_to_local_name_and_domain(actor) do + "#{actor.preferred_username}@#{Application.get_env(:my, EventosWeb.Endpoint)[:url][:host]}" + end + + @doc """ + Returns a webfinger representation of an actor + """ + def actor_to_webfinger_s(actor) do + "acct:#{actor_to_local_name_and_domain(actor)}" + end + + alias Eventos.Actors.User + + @doc """ + Returns the list of users. + + ## Examples + + iex> list_users() + [%User{}, ...] + + """ + def list_users do + Repo.all(User) + end + + def list_users_with_actors do + users = Repo.all(User) + Repo.preload(users, :actor) + end + + defp blank?(""), do: nil + defp blank?(n), do: n + + def insert_or_update_actor(data) do + data = + data + |> Map.put(:name, blank?(data[:preferred_username]) || data[:name]) + + cs = Actor.remote_actor_creation(data) + Repo.insert(cs, on_conflict: [set: [public_key: data.public_key]], conflict_target: [:preferred_username, :domain]) + end + +# def increase_event_count(%Actor{} = actor) do +# event_count = (actor.info["event_count"] || 0) + 1 +# new_info = Map.put(actor.info, "note_count", note_count) +# +# cs = info_changeset(actor, %{info: new_info}) +# +# update_and_set_cache(cs) +# end + + def count_users() do + Repo.one( + from u in User, + select: count(u.id) + ) + end + + @doc """ + Gets a single user. + + Raises `Ecto.NoResultsError` if the User does not exist. + + ## Examples + + iex> get_user!(123) + %User{} + + iex> get_user!(456) + ** (Ecto.NoResultsError) + + """ + def get_user!(id), do: Repo.get!(User, id) + + def get_user_with_actor!(id) do + user = Repo.get!(User, id) + Repo.preload(user, :actor) + end + + def get_actor_by_url(url) do + Repo.get_by(Actor, url: url) + end + + def get_actor_by_name(name) do + Repo.get_by!(Actor, preferred_username: name) + end + + def get_local_actor_by_name(name) do + Repo.one from a in Actor, where: a.preferred_username == ^name and is_nil(a.domain) + end + + def get_or_fetch_by_url(url) do + if actor = get_actor_by_url(url) do + actor + else + ap_try = ActivityPub.make_actor_from_url(url) + + case ap_try do + {:ok, actor} -> + actor + + _ -> {:error, "Could not fetch by AP id"} + end + end + end + + @doc """ + Get an user by email + """ + def find_by_email(email) do + user = Repo.get_by(User, email: email) + Repo.preload(user, :actor) + end + + @doc """ + Authenticate user + """ + def authenticate(%{user: user, password: password}) do + # Does password match the one stored in the database? + case Comeonin.Argon2.checkpw(password, user.password_hash) do + true -> + # Yes, create and return the token + EventosWeb.Guardian.encode_and_sign(user) + _ -> + # No, return an error + {:error, :unauthorized} + end + end + + @doc """ + Register user + """ + def register(%{email: email, password: password, username: username}) do + #{:ok, {privkey, pubkey}} = RsaEx.generate_keypair("4096") + {:ok, rsa_priv_key} = ExPublicKey.generate_key() + {:ok, rsa_pub_key} = ExPublicKey.public_key_from_private_key(rsa_priv_key) + + actor = Eventos.Actors.Actor.registration_changeset(%Eventos.Actors.Actor{}, %{ + preferred_username: username, + domain: nil, + private_key: rsa_priv_key |> ExPublicKey.pem_encode(), + public_key: rsa_pub_key |> ExPublicKey.pem_encode(), + url: EventosWeb.Endpoint.url() <> "/@" <> username, + }) + + user = Eventos.Actors.User.registration_changeset(%Eventos.Actors.User{}, %{ + email: email, + password: password + }) + + + actor_with_user = Ecto.Changeset.put_assoc(actor, :user, user) + + try do + Eventos.Repo.insert!(actor_with_user) + user = find_by_email(email) + {:ok, user} + rescue + e in Ecto.InvalidChangesetError -> + {:error, e.changeset.changes.user.errors} + end + end + + + @doc """ + Creates a user. + + ## Examples + + iex> create_user(%{field: value}) + {:ok, %User{}} + + iex> create_user(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_user(attrs \\ %{}) do + %User{} + |> User.registration_changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a user. + + ## Examples + + iex> update_user(user, %{field: new_value}) + {:ok, %User{}} + + iex> update_user(user, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_user(%User{} = user, attrs) do + user + |> User.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a User. + + ## Examples + + iex> delete_user(user) + {:ok, %User{}} + + iex> delete_user(user) + {:error, %Ecto.Changeset{}} + + """ + def delete_user(%User{} = user) do + Repo.delete(user) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking user changes. + + ## Examples + + iex> change_user(user) + %Ecto.Changeset{source: %User{}} + + """ + def change_user(%User{} = user) do + User.changeset(user, %{}) + end + + alias Eventos.Actors.Member + + @doc """ + Returns the list of members. + + ## Examples + + iex> list_members() + [%Member{}, ...] + + """ + def list_members do + Repo.all(Member) + end + + @doc """ + Gets a single member. + + Raises `Ecto.NoResultsError` if the Member does not exist. + + ## Examples + + iex> get_member!(123) + %Member{} + + iex> get_member!(456) + ** (Ecto.NoResultsError) + + """ + def get_member!(id), do: Repo.get!(Member, id) + + @doc """ + Creates a member. + + ## Examples + + iex> create_member(%{field: value}) + {:ok, %Member{}} + + iex> create_member(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_member(attrs \\ %{}) do + %Member{} + |> Member.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a member. + + ## Examples + + iex> update_member(member, %{field: new_value}) + {:ok, %Member{}} + + iex> update_member(member, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_member(%Member{} = member, attrs) do + member + |> Member.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a Member. + + ## Examples + + iex> delete_member(member) + {:ok, %Member{}} + + iex> delete_member(member) + {:error, %Ecto.Changeset{}} + + """ + def delete_member(%Member{} = member) do + Repo.delete(member) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking member changes. + + ## Examples + + iex> change_member(member) + %Ecto.Changeset{source: %Member{}} + + """ + def change_member(%Member{} = member) do + Member.changeset(member, %{}) + end + +end diff --git a/lib/eventos/actors/follower.ex b/lib/eventos/actors/follower.ex new file mode 100644 index 000000000..5c63098ca --- /dev/null +++ b/lib/eventos/actors/follower.ex @@ -0,0 +1,26 @@ +defmodule Eventos.Actors.Follower do + @moduledoc """ + Represents the following of an actor to another actor + """ + use Ecto.Schema + import Ecto.Changeset + alias Eventos.Actors.Follower + alias Eventos.Actors.Actor + + + schema "followers" do + field :approved, :boolean, default: false + field :score, :integer, default: 1000 + belongs_to :target_actor, Actor + belongs_to :actor, Actor + + timestamps() + end + + @doc false + def changeset(%Follower{} = member, attrs) do + member + |> cast(attrs, [:role, :approved, :target_actor_id, :actor_id]) + |> validate_required([:role, :approved, :target_actor_id, :actor_id]) + end +end diff --git a/lib/eventos/groups/member.ex b/lib/eventos/actors/member.ex similarity index 54% rename from lib/eventos/groups/member.ex rename to lib/eventos/actors/member.ex index f9ec58cbd..a4194adac 100644 --- a/lib/eventos/groups/member.ex +++ b/lib/eventos/actors/member.ex @@ -1,17 +1,18 @@ -defmodule Eventos.Groups.Member do +defmodule Eventos.Actors.Member do @moduledoc """ - Represents the membership of an account to a group + Represents the membership of an actor to a group """ use Ecto.Schema import Ecto.Changeset - alias Eventos.Groups.{Member, Group} - alias Eventos.Accounts.Account + alias Eventos.Actors.Member + alias Eventos.Actors.Actor schema "members" do + field :approved, :boolean field :role, :integer - belongs_to :group, Group - belongs_to :account, Account + belongs_to :parent, Actor + belongs_to :actor, Actor timestamps() end diff --git a/lib/eventos/accounts/user.ex b/lib/eventos/actors/user.ex similarity index 85% rename from lib/eventos/accounts/user.ex rename to lib/eventos/actors/user.ex index 1c39d77f1..dd166b8ca 100644 --- a/lib/eventos/accounts/user.ex +++ b/lib/eventos/actors/user.ex @@ -1,17 +1,17 @@ -defmodule Eventos.Accounts.User do +defmodule Eventos.Actors.User do @moduledoc """ Represents a local user """ use Ecto.Schema import Ecto.Changeset - alias Eventos.Accounts.{Account, User} + alias Eventos.Actors.{Actor, User} schema "users" do field :email, :string field :password_hash, :string field :password, :string, virtual: true field :role, :integer, default: 0 - belongs_to :account, Account + belongs_to :actor, Actor timestamps() end @@ -19,7 +19,7 @@ defmodule Eventos.Accounts.User do @doc false def changeset(%User{} = user, attrs) do user - |> cast(attrs, [:email, :role, :password_hash, :account_id]) + |> cast(attrs, [:email, :role, :password_hash, :actor_id]) |> validate_required([:email]) |> unique_constraint(:email) |> validate_format(:email, ~r/@/) diff --git a/lib/eventos/events/comment.ex b/lib/eventos/events/comment.ex index 43bc3781d..0422a452c 100644 --- a/lib/eventos/events/comment.ex +++ b/lib/eventos/events/comment.ex @@ -3,14 +3,14 @@ defmodule Eventos.Events.Comment do import Ecto.Changeset alias Eventos.Events.Event - alias Eventos.Accounts.Account - alias Eventos.Accounts.Comment + alias Eventos.Actors.Actor + alias Eventos.Actors.Comment schema "comments" do field :text, :string field :url, :string field :local, :boolean, default: true - belongs_to :account, Account, [foreign_key: :account_id] + belongs_to :actor, Actor, [foreign_key: :actor_id] belongs_to :event, Event, [foreign_key: :event_id] belongs_to :in_reply_to_comment, Comment, [foreign_key: :in_reply_to_comment_id] belongs_to :origin_comment, Comment, [foreign_key: :origin_comment_id] @@ -21,7 +21,7 @@ defmodule Eventos.Events.Comment do @doc false def changeset(comment, attrs) do comment - |> cast(attrs, [:url, :text, :account_id, :event_id, :in_reply_to_comment_id]) - |> validate_required([:url, :text, :account_id]) + |> cast(attrs, [:url, :text, :actor_id, :event_id, :in_reply_to_comment_id]) + |> validate_required([:url, :text, :actor_id]) end end diff --git a/lib/eventos/events/event.ex b/lib/eventos/events/event.ex index b9ab55596..ab9752ba9 100644 --- a/lib/eventos/events/event.ex +++ b/lib/eventos/events/event.ex @@ -34,8 +34,7 @@ defmodule Eventos.Events.Event do import Ecto.Changeset alias Eventos.Events.{Event, Participant, Request, Tag, Category, Session, Track} alias Eventos.Events.Event.TitleSlug - alias Eventos.Accounts.Account - alias Eventos.Groups.Group + alias Eventos.Actors.Actor alias Eventos.Addresses.Address schema "events" do @@ -52,11 +51,10 @@ defmodule Eventos.Events.Event do field :thumbnail, :string field :large_image, :string field :publish_at, Timex.Ecto.DateTimeWithTimezone - belongs_to :organizer_account, Account, [foreign_key: :organizer_account_id] - belongs_to :organizer_group, Group, [foreign_key: :organizer_group_id] + belongs_to :organizer_actor, Actor, [foreign_key: :organizer_actor_id] many_to_many :tags, Tag, join_through: "events_tags" belongs_to :category, Category - many_to_many :participants, Account, join_through: Participant + many_to_many :participants, Actor, join_through: Participant has_many :event_request, Request has_many :tracks, Track has_many :sessions, Session @@ -68,10 +66,10 @@ defmodule Eventos.Events.Event do @doc false def changeset(%Event{} = event, attrs) do event - |> cast(attrs, [:title, :description, :url, :begins_on, :ends_on, :organizer_account_id, :organizer_group_id, :category_id, :state, :status, :public, :thumbnail, :large_image, :publish_at]) + |> cast(attrs, [:title, :description, :url, :begins_on, :ends_on, :organizer_actor_id, :category_id, :state, :status, :public, :thumbnail, :large_image, :publish_at]) |> cast_assoc(:tags) |> cast_assoc(:address) - |> validate_required([:title, :description, :begins_on, :ends_on, :organizer_account_id, :category_id]) + |> validate_required([:title, :description, :begins_on, :ends_on, :organizer_actor_id, :category_id]) |> TitleSlug.maybe_generate_slug() |> TitleSlug.unique_constraint() end diff --git a/lib/eventos/events/events.ex b/lib/eventos/events/events.ex index a0d2e0c46..94ba1e3ed 100644 --- a/lib/eventos/events/events.ex +++ b/lib/eventos/events/events.ex @@ -8,7 +8,7 @@ defmodule Eventos.Events do alias Eventos.Events.Event alias Eventos.Events.Comment - alias Eventos.Accounts.Account + alias Eventos.Actors.Actor @doc """ Returns the list of events. @@ -23,15 +23,15 @@ defmodule Eventos.Events do Repo.all(Event) end - def get_events_for_account(%Account{id: account_id} = _account, page \\ 1, limit \\ 10) do + def get_events_for_actor(%Actor{id: actor_id} = _actor, page \\ 1, limit \\ 10) do start = (page - 1) * limit query = from e in Event, - where: e.organizer_account_id == ^account_id, + where: e.organizer_actor_id == ^actor_id, limit: ^limit, order_by: [desc: :id], offset: ^start, - preload: [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address] + preload: [:organizer_actor, :category, :sessions, :tracks, :tags, :participants, :address] events = Repo.all(query) count_events = Repo.one(from e in Event, select: count(e.id)) {:ok, events, count_events} @@ -81,7 +81,7 @@ defmodule Eventos.Events do """ def get_event_full!(id) do event = Repo.get!(Event, id) - Repo.preload(event, [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address]) + Repo.preload(event, [:organizer_actor, :category, :sessions, :tracks, :tags, :participants, :address]) end @doc """ @@ -89,18 +89,18 @@ defmodule Eventos.Events do """ def get_event_full_by_url!(url) do event = Repo.get_by(Event, url: url) - Repo.preload(event, [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address]) + Repo.preload(event, [:organizer_actor, :category, :sessions, :tracks, :tags, :participants, :address]) end - @spec get_event_full_by_username_and_slug!(String.t, String.t) :: Event.t - def get_event_full_by_username_and_slug!(username, slug) do + @spec get_event_full_by_name_and_slug!(String.t, String.t) :: Event.t + def get_event_full_by_name_and_slug!(name, slug) do event = Repo.one( from e in Event, - join: a in Account, - on: a.id == e.organizer_account_id and a.username == ^username, + join: a in Actor, + on: a.id == e.organizer_actor_id and a.name == ^name, where: e.slug == ^slug ) - Repo.preload(event, [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address]) + Repo.preload(event, [:organizer_actor, :category, :sessions, :tracks, :tags, :participants, :address]) end @doc """ @@ -389,8 +389,8 @@ defmodule Eventos.Events do ** (Ecto.NoResultsError) """ - def get_participant!(event_id, account_id) do - Repo.get_by!(Participant, [event_id: event_id, account_id: account_id]) + def get_participant!(event_id, actor_id) do + Repo.get_by!(Participant, [event_id: event_id, actor_id: actor_id]) end @doc """ @@ -458,104 +458,8 @@ defmodule Eventos.Events do Participant.changeset(participant, %{}) end - alias Eventos.Events.Request - - @doc """ - Returns the list of requests. - - ## Examples - - iex> list_requests() - [%Request{}, ...] - - """ - def list_requests do - Repo.all(Request) - end - - def list_requests_for_account(%Account{} = account) do - Repo.all(from r in Request, where: r.account_id == ^account.id) - end - - @doc """ - Gets a single request. - - Raises `Ecto.NoResultsError` if the Request does not exist. - - ## Examples - - iex> get_request!(123) - %Request{} - - iex> get_request!(456) - ** (Ecto.NoResultsError) - - """ - def get_request!(id), do: Repo.get!(Request, id) - - @doc """ - Creates a request. - - ## Examples - - iex> create_request(%{field: value}) - {:ok, %Request{}} - - iex> create_request(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_request(attrs \\ %{}) do - %Request{} - |> Request.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a request. - - ## Examples - - iex> update_request(request, %{field: new_value}) - {:ok, %Request{}} - - iex> update_request(request, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_request(%Request{} = request, attrs) do - request - |> Request.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a Request. - - ## Examples - - iex> delete_request(request) - {:ok, %Request{}} - - iex> delete_request(request) - {:error, %Ecto.Changeset{}} - - """ - def delete_request(%Request{} = request) do - Repo.delete(request) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking request changes. - - ## Examples - - iex> change_request(request) - %Ecto.Changeset{source: %Request{}} - - """ - def change_request(%Request{} = request) do - Request.changeset(request, %{}) + def list_requests_for_actor(%Actor{} = actor) do + Repo.all(from p in Participant, where: p.actor_id == ^actor.id and p.approved == false) end alias Eventos.Events.Session diff --git a/lib/eventos/events/participant.ex b/lib/eventos/events/participant.ex index f1cfed17c..4623b348f 100644 --- a/lib/eventos/events/participant.ex +++ b/lib/eventos/events/participant.ex @@ -1,17 +1,18 @@ defmodule Eventos.Events.Participant do @moduledoc """ - Represents a participant, an account participating to an event + Represents a participant, an actor participating to an event """ use Ecto.Schema import Ecto.Changeset alias Eventos.Events.{Participant, Event} - alias Eventos.Accounts.Account + alias Eventos.Actors.Actor @primary_key false schema "participants" do field :role, :integer + field :approved, :boolean belongs_to :event, Event, primary_key: true - belongs_to :account, Account, primary_key: true + belongs_to :actor, Actor, primary_key: true timestamps() end @@ -19,7 +20,7 @@ defmodule Eventos.Events.Participant do @doc false def changeset(%Participant{} = participant, attrs) do participant - |> cast(attrs, [:role, :event_id, :account_id]) - |> validate_required([:role, :event_id, :account_id]) + |> cast(attrs, [:role, :event_id, :actor_id]) + |> validate_required([:role, :event_id, :actor_id]) end end diff --git a/lib/eventos/events/request.ex b/lib/eventos/events/request.ex deleted file mode 100644 index 1bfd77a95..000000000 --- a/lib/eventos/events/request.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule Eventos.Events.Request do - @moduledoc """ - Represents an account request to join an event - """ - use Ecto.Schema - import Ecto.Changeset - alias Eventos.Events.{Request, Event} - alias Eventos.Accounts.Account - - schema "event_requests" do - field :state, :integer - belongs_to :event, Event - belongs_to :account, Account - - timestamps() - end - - @doc false - def changeset(%Request{} = request, attrs) do - request - |> cast(attrs, [:state]) - |> validate_required([:state]) - end -end diff --git a/lib/eventos/groups/group.ex b/lib/eventos/groups/group.ex deleted file mode 100644 index 05d25a914..000000000 --- a/lib/eventos/groups/group.ex +++ /dev/null @@ -1,63 +0,0 @@ -defmodule Eventos.Groups.Group.TitleSlug do - @moduledoc """ - Slug generation for groups - """ - alias Eventos.Groups.Group - import Ecto.Query - alias Eventos.Repo - use EctoAutoslugField.Slug, from: :title, to: :slug - - def build_slug(sources, changeset) do - slug = super(sources, changeset) - build_unique_slug(slug, changeset) - end - - defp build_unique_slug(slug, changeset) do - query = from g in Group, - where: g.slug == ^slug - - case Repo.one(query) do - nil -> slug - _story -> - slug - |> Eventos.Slug.increment_slug() - |> build_unique_slug(changeset) - end - end -end - -defmodule Eventos.Groups.Group do - @moduledoc """ - Represents a group - """ - use Ecto.Schema - import Ecto.Changeset - alias Eventos.Events.Event - alias Eventos.Groups.{Group, Member, Request} - alias Eventos.Accounts.Account - alias Eventos.Groups.Group.TitleSlug - alias Eventos.Addresses.Address - - schema "groups" do - field :description, :string - field :suspended, :boolean, default: false - field :title, :string - field :slug, TitleSlug.Type - field :url, :string - many_to_many :members, Account, join_through: Member - has_many :organized_events, Event, [foreign_key: :organizer_group_id] - has_many :requests, Request - belongs_to :address, Address - - timestamps() - end - - @doc false - def changeset(%Group{} = group, attrs) do - group - |> cast(attrs, [:title, :description, :suspended, :url, :address_id]) - |> validate_required([:title, :description, :suspended, :url]) - |> TitleSlug.maybe_generate_slug() - |> TitleSlug.unique_constraint() - end -end diff --git a/lib/eventos/groups/groups.ex b/lib/eventos/groups/groups.ex deleted file mode 100644 index 925b33aa0..000000000 --- a/lib/eventos/groups/groups.ex +++ /dev/null @@ -1,304 +0,0 @@ -defmodule Eventos.Groups do - @moduledoc """ - The Groups context. - """ - - import Ecto.Query, warn: false - alias Eventos.Repo - - alias Eventos.Groups.Group - - @doc """ - Returns the list of groups. - - ## Examples - - iex> list_groups() - [%Group{}, ...] - - """ - def list_groups do - Repo.all(Group) - end - - @doc """ - Gets a single group. - - Raises `Ecto.NoResultsError` if the Group does not exist. - - ## Examples - - iex> get_group!(123) - %Group{} - - iex> get_group!(456) - ** (Ecto.NoResultsError) - - """ - def get_group!(id), do: Repo.get!(Group, id) - - @doc """ - Gets a single group, with all associations loaded. - """ - def get_group_full!(id) do - group = Repo.get!(Group, id) - Repo.preload(group, [:members, :organized_events]) - end - - @doc """ - Creates a group. - - ## Examples - - iex> create_group(%{field: value}) - {:ok, %Group{}} - - iex> create_group(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_group(attrs \\ %{}) do - %Group{} - |> Group.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a group. - - ## Examples - - iex> update_group(group, %{field: new_value}) - {:ok, %Group{}} - - iex> update_group(group, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_group(%Group{} = group, attrs) do - group - |> Group.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a Group. - - ## Examples - - iex> delete_group(group) - {:ok, %Group{}} - - iex> delete_group(group) - {:error, %Ecto.Changeset{}} - - """ - def delete_group(%Group{} = group) do - Repo.delete(group) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking group changes. - - ## Examples - - iex> change_group(group) - %Ecto.Changeset{source: %Group{}} - - """ - def change_group(%Group{} = group) do - Group.changeset(group, %{}) - end - - alias Eventos.Groups.Member - - @doc """ - Returns the list of members. - - ## Examples - - iex> list_members() - [%Member{}, ...] - - """ - def list_members do - Repo.all(Member) - end - - @doc """ - Gets a single member. - - Raises `Ecto.NoResultsError` if the Member does not exist. - - ## Examples - - iex> get_member!(123) - %Member{} - - iex> get_member!(456) - ** (Ecto.NoResultsError) - - """ - def get_member!(id), do: Repo.get!(Member, id) - - @doc """ - Creates a member. - - ## Examples - - iex> create_member(%{field: value}) - {:ok, %Member{}} - - iex> create_member(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_member(attrs \\ %{}) do - %Member{} - |> Member.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a member. - - ## Examples - - iex> update_member(member, %{field: new_value}) - {:ok, %Member{}} - - iex> update_member(member, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_member(%Member{} = member, attrs) do - member - |> Member.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a Member. - - ## Examples - - iex> delete_member(member) - {:ok, %Member{}} - - iex> delete_member(member) - {:error, %Ecto.Changeset{}} - - """ - def delete_member(%Member{} = member) do - Repo.delete(member) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking member changes. - - ## Examples - - iex> change_member(member) - %Ecto.Changeset{source: %Member{}} - - """ - def change_member(%Member{} = member) do - Member.changeset(member, %{}) - end - - alias Eventos.Groups.Request - - @doc """ - Returns the list of requests. - - ## Examples - - iex> list_requests() - [%Request{}, ...] - - """ - def list_requests do - Repo.all(Request) - end - - @doc """ - Gets a single request. - - Raises `Ecto.NoResultsError` if the Request does not exist. - - ## Examples - - iex> get_request!(123) - %Request{} - - iex> get_request!(456) - ** (Ecto.NoResultsError) - - """ - def get_request!(id), do: Repo.get!(Request, id) - - @doc """ - Creates a request. - - ## Examples - - iex> create_request(%{field: value}) - {:ok, %Request{}} - - iex> create_request(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_request(attrs \\ %{}) do - %Request{} - |> Request.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a request. - - ## Examples - - iex> update_request(request, %{field: new_value}) - {:ok, %Request{}} - - iex> update_request(request, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_request(%Request{} = request, attrs) do - request - |> Request.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a Request. - - ## Examples - - iex> delete_request(request) - {:ok, %Request{}} - - iex> delete_request(request) - {:error, %Ecto.Changeset{}} - - """ - def delete_request(%Request{} = request) do - Repo.delete(request) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking request changes. - - ## Examples - - iex> change_request(request) - %Ecto.Changeset{source: %Request{}} - - """ - def change_request(%Request{} = request) do - Request.changeset(request, %{}) - end -end diff --git a/lib/eventos/groups/request.ex b/lib/eventos/groups/request.ex deleted file mode 100644 index b4cf994a6..000000000 --- a/lib/eventos/groups/request.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule Eventos.Groups.Request do - @moduledoc """ - Represents a group request, when an user wants to be member of a group - """ - use Ecto.Schema - import Ecto.Changeset - alias Eventos.Groups.Request - - - schema "group_requests" do - field :state, :integer - field :group_id, :integer - field :account_id, :integer - - timestamps() - end - - @doc false - def changeset(%Request{} = request, attrs) do - request - |> cast(attrs, [:state]) - |> validate_required([:state]) - end -end diff --git a/lib/eventos_web/controllers/account_controller.ex b/lib/eventos_web/controllers/account_controller.ex index 420c48bc9..85ee39020 100644 --- a/lib/eventos_web/controllers/account_controller.ex +++ b/lib/eventos_web/controllers/account_controller.ex @@ -1,37 +1,37 @@ -defmodule EventosWeb.AccountController do +defmodule EventosWeb.ActorController do @moduledoc """ - Controller for Accounts + Controller for Actors """ use EventosWeb, :controller - alias Eventos.Accounts - alias Eventos.Accounts.Account + alias Eventos.Actors + alias Eventos.Actors.Actor action_fallback EventosWeb.FallbackController def index(conn, _params) do - accounts = Accounts.list_accounts() - render(conn, "index.json", accounts: accounts) + actors = Actors.list_actors() + render(conn, "index.json", actors: actors) end def show(conn, %{"id" => id}) do - account = Accounts.get_account_with_everything!(id) - render(conn, "show.json", account: account) + actor = Actors.get_actor_with_everything!(id) + render(conn, "show.json", actor: actor) end - def update(conn, %{"id" => id, "account" => account_params}) do - account = Accounts.get_account!(id) + def update(conn, %{"id" => id, "actor" => actor_params}) do + actor = Actors.get_actor!(id) - with {:ok, %Account{} = account} <- Accounts.update_account(account, account_params) do - render(conn, "show.json", account: account) + with {:ok, %Actor{} = actor} <- Actors.update_actor(actor, actor_params) do + render(conn, "show.json", actor: actor) end end def delete(conn, %{"id" => id_str}) do {id, _} = Integer.parse(id_str) - if Guardian.Plug.current_resource(conn).account.id == id do - account = Accounts.get_account!(id) - with {:ok, %Account{}} <- Accounts.delete_account(account) do + if Guardian.Plug.current_resource(conn).actor.id == id do + actor = Actors.get_actor!(id) + with {:ok, %Actor{}} <- Actors.delete_actor(actor) do send_resp(conn, :no_content, "") end else diff --git a/lib/eventos_web/controllers/activity_pub_controller.ex b/lib/eventos_web/controllers/activity_pub_controller.ex index b086dc03a..6f079054d 100644 --- a/lib/eventos_web/controllers/activity_pub_controller.ex +++ b/lib/eventos_web/controllers/activity_pub_controller.ex @@ -1,7 +1,7 @@ defmodule EventosWeb.ActivityPubController do use EventosWeb, :controller - alias Eventos.{Accounts, Accounts.Account, Events, Events.Event} - alias EventosWeb.ActivityPub.{ObjectView, AccountView} + alias Eventos.{Actors, Actors.Actor, Events, Events.Event} + alias EventosWeb.ActivityPub.{ObjectView, ActorView} alias Eventos.Service.ActivityPub alias Eventos.Service.Federator @@ -9,72 +9,69 @@ defmodule EventosWeb.ActivityPubController do action_fallback(:errors) - def account(conn, %{"username" => username}) do - with %Account{} = account <- Accounts.get_account_by_username(username) do + def actor(conn, %{"name" => name}) do + with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do conn |> put_resp_header("content-type", "application/activity+json") - |> json(AccountView.render("account.json", %{account: account})) + |> json(ActorView.render("actor.json", %{actor: actor})) end end - def event(conn, %{"username" => username, "slug" => slug}) do - with %Event{} = event <- Events.get_event_full_by_username_and_slug!(username, slug) do + def event(conn, %{"name" => name, "slug" => slug}) do + with %Event{} = event <- Events.get_event_full_by_name_and_slug!(name, slug) do conn |> put_resp_header("content-type", "application/activity+json") |> json(ObjectView.render("event.json", %{event: event})) end end -# def following(conn, %{"username" => username, "page" => page}) do -# with %Account{} = account <- Accounts.get_account_by_username(username) do -# {page, _} = Integer.parse(page) -# -# conn -# |> put_resp_header("content-type", "application/activity+json") -# |> json(UserView.render("following.json", %{account: account, page: page})) -# end -# end -# -# def following(conn, %{"nickname" => nickname}) do -# with %User{} = user <- User.get_cached_by_nickname(nickname), -# {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do -# conn -# |> put_resp_header("content-type", "application/activity+json") -# |> json(UserView.render("following.json", %{user: user})) -# end -# end -# -# def followers(conn, %{"nickname" => nickname, "page" => page}) do -# with %User{} = user <- User.get_cached_by_nickname(nickname), -# {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do -# {page, _} = Integer.parse(page) -# -# conn -# |> put_resp_header("content-type", "application/activity+json") -# |> json(UserView.render("followers.json", %{user: user, page: page})) -# end -# end -# -# def followers(conn, %{"nickname" => nickname}) do -# with %User{} = user <- User.get_cached_by_nickname(nickname), -# {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do -# conn -# |> put_resp_header("content-type", "application/activity+json") -# |> json(UserView.render("followers.json", %{user: user})) -# end -# end + def following(conn, %{"name" => name, "page" => page}) do + with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do + {page, _} = Integer.parse(page) - def outbox(conn, %{"username" => username, "page" => page}) do - with {page, ""} = Integer.parse(page), - %Account{} = account <- Accounts.get_account_by_username(username) do conn |> put_resp_header("content-type", "application/activity+json") - |> json(AccountView.render("outbox.json", %{account: account, page: page})) + |> json(ActorView.render("following.json", %{actor: actor, page: page})) end end - def outbox(conn, %{"username" => username}) do - outbox(conn, %{"username" => username, "page" => "0"}) + def following(conn, %{"name" => name}) do + with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ActorView.render("following.json", %{actor: actor})) + end + end + + def followers(conn, %{"name" => name, "page" => page}) do + with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do + {page, _} = Integer.parse(page) + + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ActorView.render("followers.json", %{actor: actor, page: page})) + end + end + + def followers(conn, %{"name" => name}) do + with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ActorView.render("followers.json", %{actor: actor})) + end + end + + def outbox(conn, %{"name" => name, "page" => page}) do + with {page, ""} = Integer.parse(page), + %Actor{} = actor <- Actors.get_local_actor_by_name(name) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ActorView.render("outbox.json", %{actor: actor, page: page})) + end + end + + def outbox(conn, %{"name" => username}) do + outbox(conn, %{"name" => username, "page" => "0"}) end # TODO: Ensure that this inbox is a recipient of the message diff --git a/lib/eventos_web/controllers/event_request_controller.ex b/lib/eventos_web/controllers/event_request_controller.ex index b8d30fb24..9e25c95e7 100644 --- a/lib/eventos_web/controllers/event_request_controller.ex +++ b/lib/eventos_web/controllers/event_request_controller.ex @@ -1,52 +1,52 @@ -defmodule EventosWeb.EventRequestController do - @moduledoc """ - Controller for Event requests - """ - use EventosWeb, :controller - - alias Eventos.Events - alias Eventos.Events.Request - - action_fallback EventosWeb.FallbackController - - def index_for_user(conn, _params) do - account = Guardian.Plug.current_resource(conn).account - requests = Events.list_requests_for_account(account) - render(conn, "index.json", requests: requests) - end - - def create(conn, %{"request" => request_params}) do - request_params = Map.put(request_params, "account_id", Guardian.Plug.current_resource(conn).account.id) - with {:ok, %Request{} = request} <- Events.create_request(request_params) do - conn - |> put_status(:created) - |> put_resp_header("location", event_request_path(conn, :show, request)) - |> render("show.json", request: request) - end - end - - def create_for_event(conn, %{"request" => request_params, "id" => event_id}) do - request_params = Map.put(request_params, "event_id", event_id) - create(conn, request_params) - end - - def show(conn, %{"id" => id}) do - request = Events.get_request!(id) - render(conn, "show.json", request: request) - end - - def update(conn, %{"id" => id, "request" => request_params}) do - request = Events.get_request!(id) - - with {:ok, %Request{} = request} <- Events.update_request(request, request_params) do - render(conn, "show.json", request: request) - end - end - - def delete(conn, %{"id" => id}) do - request = Events.get_request!(id) - with {:ok, %Request{}} <- Events.delete_request(request) do - send_resp(conn, :no_content, "") - end - end -end +#defmodule EventosWeb.EventRequestController do +# @moduledoc """ +# Controller for Event requests +# """ +# use EventosWeb, :controller +# +# alias Eventos.Events +# alias Eventos.Events.{Event, Request} +# +# action_fallback EventosWeb.FallbackController +# +# def index_for_user(conn, _params) do +# actor = Guardian.Plug.current_resource(conn).actor +# requests = Events.list_requests_for_actor(actor) +# render(conn, "index.json", requests: requests) +# end +# +# def create(conn, %{"request" => request_params}) do +# request_params = Map.put(request_params, "actor_id", Guardian.Plug.current_resource(conn).actor.id) +# with {:ok, %Request{} = request} <- Events.create_request(request_params) do +# conn +# |> put_status(:created) +# |> put_resp_header("location", event_request_path(conn, :show, request)) +# |> render("show.json", request: request) +# end +# end +# +# def create_for_event(conn, %{"request" => request_params, "id" => event_id}) do +# request_params = Map.put(request_params, "event_id", event_id) +# create(conn, request_params) +# end +# +# def show(conn, %{"id" => id}) do +# request = Events.get_request!(id) +# render(conn, "show.json", request: request) +# end +# +# def update(conn, %{"id" => id, "request" => request_params}) do +# request = Events.get_request!(id) +# +# with {:ok, %Request{} = request} <- Events.update_request(request, request_params) do +# render(conn, "show.json", request: request) +# end +# end +# +# def delete(conn, %{"id" => id}) do +# request = Events.get_request!(id) +# with {:ok, %Request{}} <- Events.delete_request(request) do +# send_resp(conn, :no_content, "") +# end +# end +#end diff --git a/lib/eventos_web/controllers/group_controller.ex b/lib/eventos_web/controllers/group_controller.ex index e7daf1f3e..b1a6d9f1c 100644 --- a/lib/eventos_web/controllers/group_controller.ex +++ b/lib/eventos_web/controllers/group_controller.ex @@ -1,46 +1,46 @@ -defmodule EventosWeb.GroupController do - @moduledoc """ - Controller for Groups - """ - use EventosWeb, :controller - - alias Eventos.Groups - alias Eventos.Groups.Group - - action_fallback EventosWeb.FallbackController - - def index(conn, _params) do - groups = Groups.list_groups() - render(conn, "index.json", groups: groups) - end - - def create(conn, %{"group" => group_params}) do - group_params = Map.put(group_params, "url", "h") - with {:ok, %Group{} = group} <- Groups.create_group(group_params) do - conn - |> put_status(:created) - |> put_resp_header("location", group_path(conn, :show, group)) - |> render("show_simple.json", group: group) - end - end - - def show(conn, %{"id" => id}) do - group = Groups.get_group_full!(id) - render(conn, "show.json", group: group) - end - - def update(conn, %{"id" => id, "group" => group_params}) do - group = Groups.get_group!(id) - - with {:ok, %Group{} = group} <- Groups.update_group(group, group_params) do - render(conn, "show_simple.json", group: group) - end - end - - def delete(conn, %{"id" => id}) do - group = Groups.get_group!(id) - with {:ok, %Group{}} <- Groups.delete_group(group) do - send_resp(conn, :no_content, "") - end - end -end +#defmodule EventosWeb.GroupController do +# @moduledoc """ +# Controller for Groups +# """ +# use EventosWeb, :controller +# +# alias Eventos.Actors +# alias Eventos.Actors.Actor +# +# action_fallback EventosWeb.FallbackController +# +# def index(conn, _params) do +# groups = Actors.list_groups() +# render(conn, "index.json", groups: groups) +# end +# +# def create(conn, %{"group" => group_params}) do +# group_params = Map.put(group_params, "url", "h") +# with {:ok, %Group{} = group} <- Actors.create_group(group_params) do +# conn +# |> put_status(:created) +# |> put_resp_header("location", group_path(conn, :show, group)) +# |> render("show_simple.json", group: group) +# end +# end +# +# def show(conn, %{"id" => id}) do +# group = Actors.get_group_full!(id) +# render(conn, "show.json", group: group) +# end +# +# def update(conn, %{"id" => id, "group" => group_params}) do +# group = Actors.get_group!(id) +# +# with {:ok, %Actor{} = group} <- Actors.update_group(group, group_params) do +# render(conn, "show_simple.json", group: group) +# end +# end +# +# def delete(conn, %{"id" => id}) do +# group = Actors.get_group!(id) +# with {:ok, %Actor{}} <- Actors.delete_group(group) do +# send_resp(conn, :no_content, "") +# end +# end +#end diff --git a/lib/eventos_web/controllers/nodeinfo_controller.ex b/lib/eventos_web/controllers/nodeinfo_controller.ex index 7a4e5dfd4..c00584bc0 100644 --- a/lib/eventos_web/controllers/nodeinfo_controller.ex +++ b/lib/eventos_web/controllers/nodeinfo_controller.ex @@ -2,7 +2,7 @@ defmodule EventosWeb.NodeinfoController do use EventosWeb, :controller alias EventosWeb - alias Eventos.{Accounts, Events} + alias Eventos.{Actors, Events} @instance Application.get_env(:eventos, :instance) @@ -40,7 +40,7 @@ defmodule EventosWeb.NodeinfoController do usage: %{ users: %{ #total: stats.user_count || 0 - total: Accounts.count_users() + total: Actors.count_users() }, localPosts: Events.count_local_events(), localComments: Events.count_local_comments(), diff --git a/lib/eventos_web/controllers/outboxes_controller.ex b/lib/eventos_web/controllers/outboxes_controller.ex index bc891e5e4..f2dd0125e 100644 --- a/lib/eventos_web/controllers/outboxes_controller.ex +++ b/lib/eventos_web/controllers/outboxes_controller.ex @@ -3,8 +3,8 @@ defmodule EventosWeb.OutboxesController do use EventosWeb, :controller def show(conn) do - account = Guardian.Plug.current_resource(conn).account - events = account.events + actor = Guardian.Plug.current_resource(conn).actor + events = actor.events render(conn, "index.json", events: events) end diff --git a/lib/eventos_web/controllers/user_controller.ex b/lib/eventos_web/controllers/user_controller.ex index a2982fb95..fbbe872fc 100644 --- a/lib/eventos_web/controllers/user_controller.ex +++ b/lib/eventos_web/controllers/user_controller.ex @@ -4,19 +4,19 @@ defmodule EventosWeb.UserController do """ use EventosWeb, :controller - alias Eventos.Accounts - alias Eventos.Accounts.User + alias Eventos.Actors + alias Eventos.Actors.User alias Eventos.Repo action_fallback EventosWeb.FallbackController def index(conn, _params) do - users = Accounts.list_users_with_accounts() + users = Actors.list_users_with_actors() render(conn, "index.json", users: users) end def register(conn, %{"username" => username, "email" => email, "password" => password}) do - case Accounts.register(%{email: email, password: password, username: username}) do + case Actors.register(%{email: email, password: password, username: username}) do {:ok, %User{} = user} -> {:ok, token, _claims} = EventosWeb.Guardian.encode_and_sign(user) conn @@ -29,10 +29,10 @@ defmodule EventosWeb.UserController do end end - def show_current_account(conn, _params) do + def show_current_actor(conn, _params) do user = Guardian.Plug.current_resource(conn) user - |> Repo.preload(:account) + |> Repo.preload(:actor) render(conn, "show_simple.json", user: user) end @@ -56,16 +56,16 @@ defmodule EventosWeb.UserController do end def update(conn, %{"id" => id, "user" => user_params}) do - user = Accounts.get_user!(id) + user = Actors.get_user!(id) - with {:ok, %User{} = user} <- Accounts.update_user(user, user_params) do + with {:ok, %User{} = user} <- Actors.update_user(user, user_params) do render(conn, "show.json", user: user) end end def delete(conn, %{"id" => id}) do - user = Accounts.get_user!(id) - with {:ok, %User{}} <- Accounts.delete_user(user) do + user = Actors.get_user!(id) + with {:ok, %User{}} <- Actors.delete_user(user) do send_resp(conn, :no_content, "") end end diff --git a/lib/eventos_web/controllers/user_session_controller.ex b/lib/eventos_web/controllers/user_session_controller.ex index 0858606a8..8cfe3d755 100644 --- a/lib/eventos_web/controllers/user_session_controller.ex +++ b/lib/eventos_web/controllers/user_session_controller.ex @@ -3,14 +3,14 @@ defmodule EventosWeb.UserSessionController do Controller for user sessions """ use EventosWeb, :controller - alias Eventos.Accounts.User - alias Eventos.Accounts + alias Eventos.Actors.User + alias Eventos.Actors def sign_in(conn, %{"email" => email, "password" => password}) do - case Accounts.find_by_email(email) do + case Actors.find_by_email(email) do %User{} = user -> # Attempt to authenticate the user - case Accounts.authenticate(%{user: user, password: password}) do + case Actors.authenticate(%{user: user, password: password}) do {:ok, token, _claims} -> # Render the token render conn, "token.json", %{token: token, user: user} diff --git a/lib/eventos_web/guardian.ex b/lib/eventos_web/guardian.ex index dcb3840ee..590678ce8 100644 --- a/lib/eventos_web/guardian.ex +++ b/lib/eventos_web/guardian.ex @@ -7,8 +7,8 @@ defmodule EventosWeb.Guardian do user: [:base] } - alias Eventos.Accounts - alias Eventos.Accounts.User + alias Eventos.Actors + alias Eventos.Actors.User def subject_for_token(%User{} = user, _claims) do {:ok, "User:" <> to_string(user.id)} @@ -22,7 +22,7 @@ defmodule EventosWeb.Guardian do try do case Integer.parse(uid_str) do {uid, ""} -> - {:ok, Accounts.get_user_with_account!(uid)} + {:ok, Actors.get_user_with_actor!(uid)} _ -> {:error, :invalid_id} end diff --git a/lib/eventos_web/router.ex b/lib/eventos_web/router.ex index ea6ddc19f..8850952f8 100644 --- a/lib/eventos_web/router.ex +++ b/lib/eventos_web/router.ex @@ -9,7 +9,7 @@ defmodule EventosWeb.Router do end pipeline :well_known do - plug :accepts, ["json/application"] + plug :accepts, ["json/application", "jrd-json"] end pipeline :activity_pub do @@ -37,13 +37,13 @@ defmodule EventosWeb.Router do post "/users", UserController, :register post "/login", UserSessionController, :sign_in - resources "/groups", GroupController, only: [:index, :show] + #resources "/groups", GroupController, only: [:index, :show] resources "/events", EventController, only: [:index, :show] resources "/comments", CommentController, only: [:show] get "/events/:id/ics", EventController, :export_to_ics get "/events/:id/tracks", TrackController, :show_tracks_for_event get "/events/:id/sessions", SessionController, :show_sessions_for_event - resources "/accounts", AccountController, only: [:index, :show] + resources "/actors", ActorController, only: [:index, :show] resources "/tags", TagController, only: [:index, :show] resources "/categories", CategoryController, only: [:index, :show] resources "/sessions", SessionController, only: [:index, :show] @@ -58,19 +58,19 @@ defmodule EventosWeb.Router do scope "/v1" do - get "/user", UserController, :show_current_account + get "/user", UserController, :show_current_actor post "/sign-out", UserSessionController, :sign_out resources "/users", UserController, except: [:new, :edit, :show] - resources "/accounts", AccountController, except: [:new, :edit] + resources "/actors", ActorController, except: [:new, :edit] resources "/events", EventController resources "/comments", CommentController, except: [:new, :edit] - post "/events/:id/request", EventRequestController, :create_for_event + #post "/events/:id/request", EventRequestController, :create_for_event resources "/participant", ParticipantController - resources "/requests", EventRequestController - resources "/groups", GroupController, except: [:index, :show] - post "/groups/:id/request", GroupRequestController, :create_for_group + #resources "/requests", EventRequestController + #resources "/groups", GroupController, except: [:index, :show] + #post "/groups/:id/request", GroupRequestController, :create_for_group resources "/members", MemberController - resources "/requests", GroupRequestController + #resources "/requests", GroupRequestController resources "/sessions", SessionController, except: [:index, :show] resources "/tracks", TrackController, except: [:index, :show] get "/tracks/:id/sessions", SessionController, :show_sessions_for_track @@ -95,10 +95,12 @@ defmodule EventosWeb.Router do scope "/", EventosWeb do pipe_through :activity_pub - get "/@:username", ActivityPubController, :account - get "/@:username/outbox", ActivityPubController, :outbox - get "/@:username/:slug", ActivityPubController, :event - post "/@:username/inbox", ActivityPubController, :inbox + get "/@:name", ActivityPubController, :actor + get "/@:name/outbox", ActivityPubController, :outbox + get "/@:name/following", ActivityPubController, :following + get "/@:name/followers", ActivityPubController, :followers + get "/@:name/:slug", ActivityPubController, :event + post "/@:name/inbox", ActivityPubController, :inbox post "/inbox", ActivityPubController, :inbox end diff --git a/lib/eventos_web/views/account_view.ex b/lib/eventos_web/views/account_view.ex index e2475f997..51a4d7474 100644 --- a/lib/eventos_web/views/account_view.ex +++ b/lib/eventos_web/views/account_view.ex @@ -1,48 +1,44 @@ -defmodule EventosWeb.AccountView do +defmodule EventosWeb.ActorView do @moduledoc """ - View for Accounts + View for Actors """ use EventosWeb, :view - alias EventosWeb.{AccountView, EventView} + alias EventosWeb.{ActorView, EventView} - def render("index.json", %{accounts: accounts}) do - %{data: render_many(accounts, AccountView, "acccount_basic.json")} + def render("index.json", %{actors: actors}) do + %{data: render_many(actors, ActorView, "acccount_basic.json")} end - def render("show.json", %{account: account}) do - %{data: render_one(account, AccountView, "account.json")} + def render("show.json", %{actor: actor}) do + %{data: render_one(actor, ActorView, "actor.json")} end - def render("show_basic.json", %{account: account}) do - %{data: render_one(account, AccountView, "account_basic.json")} + def render("show_basic.json", %{actor: actor}) do + %{data: render_one(actor, ActorView, "actor_basic.json")} end - def render("acccount_basic.json", %{account: account}) do - %{id: account.id, - username: account.username, - domain: account.domain, - display_name: account.display_name, - description: account.description, - # public_key: account.public_key, - suspended: account.suspended, - url: account.url, - avatar_url: account.avatar_url, - banner_url: account.banner_url, + def render("acccount_basic.json", %{actor: actor}) do + %{id: actor.id, + username: actor.username, + domain: actor.domain, + display_name: actor.display_name, + description: actor.description, + # public_key: actor.public_key, + suspended: actor.suspended, + url: actor.url, } end - def render("account.json", %{account: account}) do - %{id: account.id, - username: account.username, - domain: account.domain, - display_name: account.display_name, - description: account.description, - # public_key: account.public_key, - suspended: account.suspended, - url: account.url, - avatar_url: account.avatar_url, - banner_url: account.banner_url, - organized_events: render_many(account.organized_events, EventView, "event_simple.json") + def render("actor.json", %{actor: actor}) do + %{id: actor.id, + username: actor.username, + domain: actor.domain, + display_name: actor.display_name, + description: actor.description, + # public_key: actor.public_key, + suspended: actor.suspended, + url: actor.url, + organized_events: render_many(actor.organized_events, EventView, "event_simple.json") } end end diff --git a/lib/eventos_web/views/activity_pub/account_view.ex b/lib/eventos_web/views/activity_pub/account_view.ex deleted file mode 100644 index 1163e541f..000000000 --- a/lib/eventos_web/views/activity_pub/account_view.ex +++ /dev/null @@ -1,117 +0,0 @@ -defmodule EventosWeb.ActivityPub.AccountView do - use EventosWeb, :view - - alias EventosWeb.ActivityPub.AccountView - alias EventosWeb.ActivityPub.ObjectView - alias EventosWeb.WebFinger - alias Eventos.Accounts.Account - alias Eventos.Repo - alias Eventos.Service.ActivityPub - alias Eventos.Service.ActivityPub.Transmogrifier - alias Eventos.Service.ActivityPub.Utils - import Ecto.Query - - def render("account.json", %{account: account}) do - {:ok, public_key} = Account.get_public_key_for_account(account) - - %{ - "id" => account.url, - "type" => "Person", - #"following" => "#{account.url}/following", - #"followers" => "#{account.url}/followers", - "inbox" => "#{account.url}/inbox", - "outbox" => "#{account.url}/outbox", - "preferredUsername" => account.username, - "name" => account.display_name, - "summary" => account.description, - "url" => account.url, - #"manuallyApprovesFollowers" => false, - "publicKey" => %{ - "id" => "#{account.url}#main-key", - "owner" => account.url, - "publicKeyPem" => public_key - }, - "endpoints" => %{ - "sharedInbox" => "#{EventosWeb.Endpoint.url()}/inbox" - }, -# "icon" => %{ -# "type" => "Image", -# "url" => User.avatar_url(account) -# }, -# "image" => %{ -# "type" => "Image", -# "url" => User.banner_url(account) -# } - } - |> Map.merge(Utils.make_json_ld_header()) - end - - def render("outbox.json", %{account: account, page: page}) do - {page, no_page} = if page == 0 do - {1, true} - else - {page, false} - end - - {activities, total} = ActivityPub.fetch_public_activities_for_account(account, page) - - collection = - Enum.map(activities, fn act -> - {:ok, data} = Transmogrifier.prepare_outgoing(act.data) - data - end) - - iri = "#{account.url}/outbox" - - page = %{ - "id" => "#{iri}?page=#{page}", - "type" => "OrderedCollectionPage", - "partOf" => iri, - "totalItems" => total, - "orderedItems" => render_many(activities, AccountView, "activity.json", as: :activity), - "next" => "#{iri}?page=#{page + 1}" - } - - if no_page do - %{ - "id" => iri, - "type" => "OrderedCollection", - "totalItems" => total, - "first" => page - } - |> Map.merge(Utils.make_json_ld_header()) - else - page |> Map.merge(Utils.make_json_ld_header()) - end - end - - def render("activity.json", %{activity: activity}) do - %{ - "id" => activity.data.url <> "/activity", - "type" => "Create", - "actor" => activity.data.organizer_account.url, - "published" => Timex.now(), - "to" => ["https://www.w3.org/ns/activitystreams#Public"], - "object" => render_one(activity.data, ObjectView, "event.json", as: :event) - } - end - - def collection(collection, iri, page, total \\ nil) do - offset = (page - 1) * 10 - items = Enum.slice(collection, offset, 10) - items = Enum.map(items, fn user -> user.ap_id end) - total = total || length(collection) - - map = %{ - "id" => "#{iri}?page=#{page}", - "type" => "OrderedCollectionPage", - "partOf" => iri, - "totalItems" => total, - "orderedItems" => items - } - - if offset < total do - Map.put(map, "next", "#{iri}?page=#{page + 1}") - end - end -end diff --git a/lib/eventos_web/views/activity_pub/actor_view.ex b/lib/eventos_web/views/activity_pub/actor_view.ex new file mode 100644 index 000000000..90e3948e9 --- /dev/null +++ b/lib/eventos_web/views/activity_pub/actor_view.ex @@ -0,0 +1,155 @@ +defmodule EventosWeb.ActivityPub.ActorView do + use EventosWeb, :view + + alias EventosWeb.ActivityPub.ActorView + alias EventosWeb.ActivityPub.ObjectView + alias EventosWeb.WebFinger + alias Eventos.Actors.Actor + alias Eventos.Repo + alias Eventos.Service.ActivityPub + alias Eventos.Service.ActivityPub.Transmogrifier + alias Eventos.Service.ActivityPub.Utils + import Ecto.Query + + def render("actor.json", %{actor: actor}) do + {:ok, public_key} = Actor.get_public_key_for_actor(actor) + + %{ + "id" => actor.url, + "type" => "Person", + "following" => actor.following_url, + "followers" => actor.followers_url, + "inbox" => actor.inbox_url, + "outbox" => actor.outbox_url, + "preferredUsername" => actor.preferred_username, + "name" => actor.name, + "summary" => actor.summary, + "url" => actor.url, + "manuallyApprovesFollowers" => actor.manually_approves_followers, + "publicKey" => %{ + "id" => "#{actor.url}#main-key", + "owner" => actor.url, + "publicKeyPem" => public_key + }, + "endpoints" => %{ + "sharedInbox" => actor.shared_inbox_url, + }, +# "icon" => %{ +# "type" => "Image", +# "url" => User.avatar_url(actor) +# }, +# "image" => %{ +# "type" => "Image", +# "url" => User.banner_url(actor) +# } + } + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("following.json", %{actor: actor, page: page}) do + following = Actor.get_followings(actor) + + collection(following, actor.following_url, page) + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("following.json", %{actor: actor}) do + following = Actor.get_followings(actor) + + %{ + "id" => actor.following_url, + "type" => "OrderedCollection", + "totalItems" => length(following), + "first" => collection(following, actor.following_url, 1) + } + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("followers.json", %{actor: actor, page: page}) do + followers = Actor.get_followers(actor) + + collection(followers, actor.followers_url, page) + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("followers.json", %{actor: actor}) do + followers = Actor.get_followers(actor) + + %{ + "id" => actor.followers_url, + "type" => "OrderedCollection", + "totalItems" => length(followers), + "first" => collection(followers, actor.followers_url, 1) + } + |> Map.merge(Utils.make_json_ld_header()) + end + + def render("outbox.json", %{actor: actor, page: page}) do + {page, no_page} = if page == 0 do + {1, true} + else + {page, false} + end + + {activities, total} = ActivityPub.fetch_public_activities_for_actor(actor, page) + + collection = + Enum.map(activities, fn act -> + {:ok, data} = Transmogrifier.prepare_outgoing(act.data) + data + end) + + iri = "#{actor.url}/outbox" + + page = %{ + "id" => "#{iri}?page=#{page}", + "type" => "OrderedCollectionPage", + "partOf" => iri, + "totalItems" => total, + "orderedItems" => render_many(activities, ActorView, "activity.json", as: :activity), + "next" => "#{iri}?page=#{page + 1}" + } + + if no_page do + %{ + "id" => iri, + "type" => "OrderedCollection", + "totalItems" => total, + "first" => page + } + |> Map.merge(Utils.make_json_ld_header()) + else + page |> Map.merge(Utils.make_json_ld_header()) + end + end + + def render("activity.json", %{activity: activity}) do + %{ + "id" => activity.data.url <> "/activity", + "type" => "Create", + "actor" => activity.data.organizer_actor.url, + "published" => Timex.now(), + "to" => ["https://www.w3.org/ns/activitystreams#Public"], + "object" => render_one(activity.data, ObjectView, "event.json", as: :event) + } + end + + def collection(collection, iri, page, total \\ nil) do + offset = (page - 1) * 10 + items = Enum.slice(collection, offset, 10) + items = Enum.map(items, fn account -> account.url end) + total = total || length(collection) + + map = %{ + "id" => "#{iri}?page=#{page}", + "type" => "OrderedCollectionPage", + "partOf" => iri, + "totalItems" => total, + "orderedItems" => items + } + + if offset < total do + Map.put(map, "next", "#{iri}?page=#{page + 1}") + end + end +end diff --git a/lib/eventos_web/views/event_view.ex b/lib/eventos_web/views/event_view.ex index 771836f74..3bfe03023 100644 --- a/lib/eventos_web/views/event_view.ex +++ b/lib/eventos_web/views/event_view.ex @@ -3,7 +3,7 @@ defmodule EventosWeb.EventView do View for Events """ use EventosWeb, :view - alias EventosWeb.{EventView, AccountView, GroupView, AddressView} + alias EventosWeb.{EventView, ActorView, GroupView, AddressView} def render("index.json", %{events: events}) do %{data: render_many(events, EventView, "event_simple.json")} @@ -32,9 +32,9 @@ defmodule EventosWeb.EventView do description: event.description, begins_on: event.begins_on, ends_on: event.ends_on, - organizer: render_one(event.organizer_account, AccountView, "acccount_basic.json"), + organizer: render_one(event.organizer_actor, ActorView, "acccount_basic.json"), group: render_one(event.organizer_group, GroupView, "group_basic.json"), - participants: render_many(event.participants, AccountView, "show_basic.json"), + participants: render_many(event.participants, ActorView, "show_basic.json"), address: render_one(event.address, AddressView, "address.json"), } end diff --git a/lib/eventos_web/views/group_view.ex b/lib/eventos_web/views/group_view.ex index 1e454a148..132f91d3e 100644 --- a/lib/eventos_web/views/group_view.ex +++ b/lib/eventos_web/views/group_view.ex @@ -3,7 +3,7 @@ defmodule EventosWeb.GroupView do View for Groups """ use EventosWeb, :view - alias EventosWeb.{GroupView, AccountView} + alias EventosWeb.{GroupView, ActorView} def render("index.json", %{groups: groups}) do %{data: render_many(groups, GroupView, "group_simple.json")} @@ -32,7 +32,7 @@ defmodule EventosWeb.GroupView do description: group.description, suspended: group.suspended, url: group.url, - members: render_many(group.members, AccountView, "acccount_basic.json"), + members: render_many(group.members, ActorView, "acccount_basic.json"), events: render_many(group.organized_events, EventView, "event_simple.json") } end diff --git a/lib/eventos_web/views/user_view.ex b/lib/eventos_web/views/user_view.ex index 9d7ac9d1f..600a3f07c 100644 --- a/lib/eventos_web/views/user_view.ex +++ b/lib/eventos_web/views/user_view.ex @@ -4,7 +4,7 @@ defmodule EventosWeb.UserView do """ use EventosWeb, :view alias EventosWeb.UserView - alias EventosWeb.AccountView + alias EventosWeb.ActorView def render("index.json", %{users: users}) do %{data: render_many(users, UserView, "user_simple.json")} @@ -28,14 +28,14 @@ defmodule EventosWeb.UserView do def render("user_simple.json", %{user: user}) do %{id: user.id, role: user.role, - account: render_one(user.account, AccountView, "acccount_basic.json") + actor: render_one(user.actor, ActorView, "acccount_basic.json") } end def render("user.json", %{user: user}) do %{id: user.id, role: user.role, - account: render_one(user.account, AccountView, "account.json") + actor: render_one(user.actor, ActorView, "actor.json") } end diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 9c775bc30..cd5360650 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -5,8 +5,8 @@ defmodule Eventos.Service.ActivityPub do alias Eventos.Service.WebFinger alias Eventos.Activity - alias Eventos.Accounts - alias Eventos.Accounts.Account + alias Eventos.Actors + alias Eventos.Actors.Actor alias Eventos.Service.Federator @@ -83,8 +83,12 @@ defmodule Eventos.Service.ActivityPub do ), {:ok, activity} <- insert(create_data, local), :ok <- maybe_federate(activity) do - # {:ok, actor} <- Accounts.increase_event_count(actor) do + # {:ok, actor} <- Actors.increase_event_count(actor) do {:ok, activity} + else + err -> + Logger.debug("Something went wrong") + Logger.debug(inspect err) end end @@ -124,13 +128,13 @@ defmodule Eventos.Service.ActivityPub do end end - def delete(%Event{url: url, organizer_account: account} = event, local \\ true) do + def delete(%Event{url: url, organizer_actor: actor} = event, local \\ true) do data = %{ "type" => "Delete", - "actor" => account.url, + "actor" => actor.url, "object" => url, - "to" => [account.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"] + "to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"] } with Events.delete_event(event), @@ -141,40 +145,43 @@ defmodule Eventos.Service.ActivityPub do end end - def create_public_activities(%Account{} = account) do + def create_public_activities(%Actor{} = actor) do end - def make_account_from_url(url) do + def make_actor_from_url(url) do with {:ok, data} <- fetch_and_prepare_user_from_url(url) do - Accounts.insert_or_update_account(data) + Actors.insert_or_update_actor(data) else e -> - Logger.error("Failed to make account from url") + Logger.error("Failed to make actor from url") Logger.error(inspect e) {:error, e} end end - def make_account_from_nickname(nickname) do + def make_actor_from_nickname(nickname) do with {:ok, %{"url" => url}} when not is_nil(url) <- WebFinger.finger(nickname) do - make_account_from_url(url) + make_actor_from_url(url) else _e -> {:error, "No ActivityPub URL found in WebFinger"} end end def publish(actor, activity) do -# followers = -# if actor.follower_address in activity.recipients do -# {:ok, followers} = User.get_followers(actor) -# followers |> Enum.filter(&(!&1.local)) -# else -# [] -# end - followers = ["http://localhost:3000/users/tcit/inbox"] + Logger.debug("Publishing an activity") + followers = + if actor.followers_url in activity.recipients do + {:ok, followers} = Actor.get_followers(actor) + followers |> Enum.filter(fn follower -> is_nil(follower.domain) end) + else + [] + end - remote_inboxes = followers + remote_inboxes = + followers + |> Enum.map(fn follower -> follower.shared_inbox_url end) + |> Enum.uniq() {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) json = Jason.encode!(data) @@ -219,6 +226,8 @@ defmodule Eventos.Service.ActivityPub do end def user_data_from_user_object(data) do + Logger.debug("user_data_from_user_object") + Logger.debug(inspect data) avatar = data["icon"]["url"] && %{ @@ -241,19 +250,27 @@ defmodule Eventos.Service.ActivityPub do "banner" => banner }, avatar: avatar, - username: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}", - display_name: data["name"], + name: data["name"], + preferred_username: data["preferredUsername"], follower_address: data["followers"], - description: data["summary"], + summary: data["summary"], public_key: data["publicKey"]["publicKeyPem"], + inbox_url: data["inbox"], + outbox_url: data["outbox"], + following_url: data["following"], + followers_url: data["followers"], + shared_inbox_url: data["sharedInbox"], + domain: URI.parse(data["id"]).host, + manually_approves_followers: data["manuallyApprovesFollowers"], + type: data["type"], } {:ok, user_data} end - @spec fetch_public_activities_for_account(Account.t, integer(), integer()) :: list() - def fetch_public_activities_for_account(%Account{} = account, page \\ 10, limit \\ 1) do - {:ok, events, total} = Events.get_events_for_account(account, page, limit) + @spec fetch_public_activities_for_actor(Actor.t, integer(), integer()) :: list() + def fetch_public_activities_for_actor(%Actor{} = actor, page \\ 10, limit \\ 1) do + {:ok, events, total} = Events.get_events_for_actor(actor, page, limit) activities = Enum.map(events, fn event -> {:ok, activity} = event_to_activity(event) activity @@ -265,7 +282,7 @@ defmodule Eventos.Service.ActivityPub do activity = %Activity{ data: event, local: true, - actor: event.organizer_account.url, + actor: event.organizer_actor.url, recipients: ["https://www.w3.org/ns/activitystreams#Public"] } diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex index 774293025..02e9ea446 100644 --- a/lib/service/activity_pub/transmogrifier.ex +++ b/lib/service/activity_pub/transmogrifier.ex @@ -2,8 +2,8 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do @moduledoc """ A module to handle coding from internal to wire ActivityPub and back. """ - alias Eventos.Accounts.Account - alias Eventos.Accounts + alias Eventos.Actors.Actor + alias Eventos.Actors alias Eventos.Events.Event alias Eventos.Service.ActivityPub @@ -101,13 +101,15 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do # - tags # - emoji def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do - with %Account{} = account <- Account.get_or_fetch_by_url(data["actor"]) do + Logger.debug("Handle incoming to create notes") + with %Actor{} = actor <- Actor.get_or_fetch_by_url(data["actor"]) do + Logger.debug("found actor") object = fix_object(data["object"]) params = %{ to: data["to"], object: object, - actor: account, + actor: actor, context: object["conversation"], local: false, published: data["published"], @@ -122,15 +124,13 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do end end - def handle_incoming( - %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data - ) do - with %Account{} = followed <- Accounts.get_account_by_url(followed), - %Account{} = follower <- Accounts.get_or_fetch_by_url(follower), + def handle_incoming(%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data) do + with %Actor{} = followed <- Actors.get_actor_by_url(followed), + %Actor{} = follower <- Actors.get_or_fetch_by_url(follower), {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true}) - #Accounts.follow(follower, followed) + #Actors.follow(follower, followed) {:ok, activity} else _e -> :error diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex index 6cd73bda2..087a64242 100644 --- a/lib/service/activity_pub/utils.ex +++ b/lib/service/activity_pub/utils.ex @@ -1,7 +1,7 @@ defmodule Eventos.Service.ActivityPub.Utils do alias Eventos.Repo - alias Eventos.Accounts - alias Eventos.Accounts.Account + alias Eventos.Actors + alias Eventos.Actors.Actor alias Eventos.Events.Event alias Eventos.Events alias Eventos.Activity @@ -126,8 +126,11 @@ defmodule Eventos.Service.ActivityPub.Utils do """ def insert_full_object(%{"object" => %{"type" => type} = object_data}) when is_map(object_data) and type == "Note" do - account = Accounts.get_account_by_url(object_data["actor"]) - data = %{"text" => object_data["content"], "url" => object_data["url"], "account_id" => account.id, "in_reply_to_comment_id" => object_data["inReplyTo"]} + import Logger + Logger.debug("insert full object") + Logger.debug(inspect object_data) + actor = Actors.get_actor_by_url(object_data["actor"]) + data = %{"text" => object_data["content"], "url" => object_data["id"], "actor_id" => actor.id, "in_reply_to_comment_id" => object_data["inReplyTo"]} with {:ok, _} <- Events.create_comment(data) do :ok end @@ -173,7 +176,7 @@ defmodule Eventos.Service.ActivityPub.Utils do # Repo.one(query) # end - def make_like_data(%Account{url: url} = actor, %{data: %{"id" => id}} = object, activity_id) do + def make_like_data(%Actor{url: url} = actor, %{data: %{"id" => id}} = object, activity_id) do data = %{ "type" => "Like", "actor" => url, @@ -218,7 +221,7 @@ defmodule Eventos.Service.ActivityPub.Utils do @doc """ Makes a follow activity data for the given follower and followed """ - def make_follow_data(%Account{url: follower_id}, %Account{url: followed_id}, activity_id) do + def make_follow_data(%Actor{url: follower_id}, %Actor{url: followed_id}, activity_id) do data = %{ "type" => "Follow", "actor" => follower_id, @@ -230,7 +233,7 @@ defmodule Eventos.Service.ActivityPub.Utils do if activity_id, do: Map.put(data, "id", activity_id), else: data end -# def fetch_latest_follow(%Account{url: follower_id}, %Account{url: followed_id}) do +# def fetch_latest_follow(%Actor{url: follower_id}, %Actor{url: followed_id}) do # query = # from( # activity in Activity, @@ -253,7 +256,7 @@ defmodule Eventos.Service.ActivityPub.Utils do Make announce activity data for the given actor and object """ def make_announce_data( - %Account{url: url} = user, + %Actor{url: url} = user, %Event{id: id} = object, activity_id ) do diff --git a/lib/service/federator.ex b/lib/service/federator.ex index fbc240b65..2065537af 100644 --- a/lib/service/federator.ex +++ b/lib/service/federator.ex @@ -1,6 +1,6 @@ defmodule Eventos.Service.Federator do use GenServer - alias Eventos.Accounts + alias Eventos.Actors alias Eventos.Activity alias Eventos.Service.ActivityPub alias Eventos.Service.ActivityPub.Transmogrifier @@ -33,7 +33,7 @@ defmodule Eventos.Service.Federator do Logger.debug(inspect activity) Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) - with actor when not is_nil(actor) <- Accounts.get_account_by_url(activity.data["actor"]) do + with actor when not is_nil(actor) <- Actors.get_actor_by_url(activity.data["actor"]) do Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end) ActivityPub.publish(actor, activity) diff --git a/lib/service/http_signatures/http_signatures.ex b/lib/service/http_signatures/http_signatures.ex index 14dcc32b8..517f29650 100644 --- a/lib/service/http_signatures/http_signatures.ex +++ b/lib/service/http_signatures/http_signatures.ex @@ -1,6 +1,6 @@ # https://tools.ietf.org/html/draft-cavage-http-signatures-08 defmodule Eventos.Service.HTTPSignatures do - alias Eventos.Accounts.Account + alias Eventos.Actors.Actor alias Eventos.Service.ActivityPub require Logger @@ -25,52 +25,44 @@ defmodule Eventos.Service.HTTPSignatures do Logger.debug("Signature: #{signature["signature"]}") Logger.debug("Sigstring: #{sigstring}") {:ok, sig} = Base.decode64(signature["signature"]) - Logger.debug(inspect sig) - Logger.debug(inspect public_key) - case ExPublicKey.verify(sigstring, sig, public_key) do - {:ok, sig_valid} -> - sig_valid - {:error, err} -> - Logger.error(err) - false - end + :public_key.verify(sigstring, :sha256, sig, public_key) end def validate_conn(conn) do # TODO: How to get the right key and see if it is actually valid for that request. # For now, fetch the key for the actor. with actor_id <- conn.params["actor"], - {:ok, public_key} <- Account.get_public_key_for_url(actor_id) do - case HTTPSign.verify(conn, public_key) do - {:ok, conn} -> - true - _ -> - Logger.debug("Could not validate, re-fetching user and trying one more time") + {:ok, public_key_code} <- Actor.get_public_key_for_url(actor_id), + [public_key] = :public_key.pem_decode(public_key_code), + public_key = :public_key.pem_entry_decode(public_key) do + if validate_conn(conn, public_key) do + true + else + Logger.info("Could not validate request, re-fetching user and trying one more time") # Fetch user anew and try one more time with actor_id <- conn.params["actor"], - {:ok, _user} <- ActivityPub.make_account_from_url(actor_id), - {:ok, public_key} <- Account.get_public_key_for_url(actor_id) do - case HTTPSign.verify(conn, public_key) do - {:ok, conn} -> - true - {:error, :forbidden} -> - false - end + {:ok, _actor} <- ActivityPub.make_actor_from_url(actor_id), + {:ok, public_key_code} <- Actor.get_public_key_for_url(actor_id), + [public_key] = :public_key.pem_decode(public_key_code), + public_key = :public_key.pem_entry_decode(public_key) do + validate_conn(conn, public_key) end end else e -> - Logger.debug("Could not public key!") + Logger.debug("Could not found url for actor!") Logger.debug(inspect e) false end end -# def validate_conn(conn, public_key) do -# headers = Enum.into(conn.req_headers, %{}) -# signature = split_signature(headers["signature"]) -# validate(headers, signature, public_key) -# end + def validate_conn(conn, public_key) do + headers = Enum.into(conn.req_headers, %{}) + [host_without_port, _] = String.split(headers["host"], ":") + headers = Map.put(headers, "host", host_without_port) + signature = split_signature(headers["signature"]) + validate(headers, signature, public_key) + end def build_signing_string(headers, used_headers) do used_headers @@ -78,35 +70,24 @@ defmodule Eventos.Service.HTTPSignatures do |> Enum.join("\n") end - def sign(account, headers) do - sigstring = build_signing_string(headers, Map.keys(headers)) + def sign(actor, headers) do + with {:ok, private_key_code} = Actor.get_private_key_for_actor(actor), + [private_key] = :public_key.pem_decode(private_key_code), + private_key = :public_key.pem_entry_decode(private_key) do + sigstring = build_signing_string(headers, Map.keys(headers)) - {:ok, private_key} = Account.get_private_key_for_account(account) + signature = + :public_key.sign(sigstring, :sha256, private_key) + |> Base.encode64() - Logger.debug("private_key") - Logger.debug(inspect private_key) - Logger.debug("sigstring") - Logger.debug(inspect sigstring) - {:ok, signature} = HTTPSign.Crypto.sign(:rsa, sigstring, private_key) - Logger.debug(inspect signature) - - signature = Base.encode64(signature) - - sign = [ - keyId: account.url <> "#main-key", - algorithm: "rsa-sha256", - headers: Map.keys(headers) |> Enum.join(" "), - signature: signature - ] - |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end) - |> Enum.join(",") - - Logger.debug("sign") - Logger.debug(inspect sign) - {:ok, public_key} = Account.get_public_key_for_account(account) - Logger.debug("inspect split signature inside sign") - Logger.debug(inspect split_signature(sign)) - Logger.debug(inspect validate(headers, split_signature(sign), public_key)) - sign + [ + keyId: actor.url <> "#main-key", + algorithm: "rsa-sha256", + headers: Map.keys(headers) |> Enum.join(" "), + signature: signature + ] + |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end) + |> Enum.join(",") + end end end diff --git a/lib/service/web_finger/web_finger.ex b/lib/service/web_finger/web_finger.ex index cb263eaf8..364c39654 100644 --- a/lib/service/web_finger/web_finger.ex +++ b/lib/service/web_finger/web_finger.ex @@ -1,6 +1,6 @@ defmodule Eventos.Service.WebFinger do - alias Eventos.Accounts + alias Eventos.Actors alias Eventos.Service.XmlBuilder alias Eventos.Repo require Jason @@ -26,14 +26,14 @@ defmodule Eventos.Service.WebFinger do def webfinger(resource, "JSON") do host = EventosWeb.Endpoint.host() - regex = ~r/(acct:)?(?\w+)@#{host}/ + regex = ~r/(acct:)?(?\w+)@#{host}/ - with %{"username" => username} <- Regex.named_captures(regex, resource) do - user = Accounts.get_account_by_username(username) + with %{"name" => name} <- Regex.named_captures(regex, resource) do + user = Actors.get_local_actor_by_name(name) {:ok, represent_user(user, "JSON")} else _e -> - with user when not is_nil(user) <- Accounts.get_account_by_url(resource) do + with user when not is_nil(user) <- Actors.get_actor_by_url(resource) do {:ok, represent_user(user, "JSON")} else _e -> @@ -44,7 +44,7 @@ defmodule Eventos.Service.WebFinger do def represent_user(user, "JSON") do %{ - "subject" => "acct:#{user.username}@#{EventosWeb.Endpoint.host() <> ":4001"}", + "subject" => "acct:#{user.preferred_username}@#{EventosWeb.Endpoint.host() <> ":4001"}", "aliases" => [user.url], "links" => [ %{"rel" => "self", "type" => "application/activity+json", "href" => user.url}, @@ -67,18 +67,18 @@ defmodule Eventos.Service.WebFinger do {:ok, data} end - def finger(account) do - account = String.trim_leading(account, "@") + def finger(actor) do + actor = String.trim_leading(actor, "@") domain = - with [_name, domain] <- String.split(account, "@") do + with [_name, domain] <- String.split(actor, "@") do domain else _e -> - URI.parse(account).host + URI.parse(actor).host end - address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}" + address = "http://#{domain}/.well-known/webfinger?resource=acct:#{actor}" with response <- HTTPoison.get(address, [Accept: "application/json"],follow_redirect: true), {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- response do @@ -86,7 +86,7 @@ defmodule Eventos.Service.WebFinger do webfinger_from_json(doc) else e -> - Logger.debug(fn -> "Couldn't finger #{account}" end) + Logger.debug(fn -> "Couldn't finger #{actor}" end) Logger.debug(fn -> inspect(e) end) {:error, e} end diff --git a/mix.exs b/mix.exs index b8ed0b51f..2c3dfb416 100644 --- a/mix.exs +++ b/mix.exs @@ -65,6 +65,7 @@ defmodule Eventos.Mixfile do {:jason, "~> 1.0"}, {:ex_crypto, "~> 0.9.0"}, {:http_sign, "~> 0.1.1"}, + {:ecto_enum, "~> 1.0"}, # Dev and test dependencies {:phoenix_live_reload, "~> 1.0", only: :dev}, {:ex_machina, "~> 2.1", only: :test}, diff --git a/mix.lock b/mix.lock index 5d4c0c916..795a6c5da 100644 --- a/mix.lock +++ b/mix.lock @@ -15,6 +15,7 @@ "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_autoslug_field": {:hex, :ecto_autoslug_field, "0.5.1", "c8a160fa6e5e0002740fe1c500bcc27d10bdb073a93715ce8a01b7af8a290777", [:mix], [{:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.2.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm"}, + "ecto_enum": {:hex, :ecto_enum, "1.1.0", "d44fe2ce6e1c0e907e7c3b6456a69e0f1d662348d8b4e2a662ba312223d8ff62", [:mix], [{:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.1", "6628b86053190a80b9072382bb9756a6c78624f208ec0ff22cb94c8977d80060", [:mix], [], "hexpm"}, "ex_crypto": {:hex, :ex_crypto, "0.9.0", "e04a831034c4d0a43fb2858f696d6b5ae0f87f07dedca3452912fd3cb5ee3ca2", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/priv/repo/migrations/20180517100700_move_from_account_to_actor.exs b/priv/repo/migrations/20180517100700_move_from_account_to_actor.exs new file mode 100644 index 000000000..2efe98c83 --- /dev/null +++ b/priv/repo/migrations/20180517100700_move_from_account_to_actor.exs @@ -0,0 +1,98 @@ +defmodule Eventos.Repo.Migrations.MoveFromAccountToActor do + use Ecto.Migration + + def up do + drop table("event_requests") + drop table("group_requests") + + + alter table("events") do + remove :organizer_group_id + end + + rename table("members"), :account_id, to: :actor_id + + alter table("members") do + remove :group_id + add :parent_id, references(:accounts, on_delete: :nothing) + end + + drop table("groups") + rename table("accounts"), to: table("actors") + Eventos.Actors.ActorTypeEnum.create_type + rename table("actors"), :username, to: :name + rename table("actors"), :description, to: :summary + rename table("actors"), :display_name, to: :preferred_username + alter table("actors") do + add :inbox_url, :string + add :outbox_url, :string + add :following_url, :string + add :followers_url, :string + add :shared_inbox_url, :string + add :type, :actor_type + add :manually_approves_followers, :boolean + modify :name, :string, null: true + modify :preferred_username, :string, null: false + end + + create unique_index(:actors, [:preferred_username, :domain]) + + rename table("events"), :organizer_account_id, to: :organizer_actor_id + + rename table("participants"), :account_id, to: :actor_id + + create table("followers") do + add :approved, :boolean, default: false + add :score, :integer, default: 1000 + add :actor_id, references(:actors, on_delete: :nothing) + add :target_actor_id, references(:actors, on_delete: :nothing) + end + + rename table("comments"), :account_id, to: :actor_id + end + + def down do + create table("event_requests") + create table("group_requests") + + + alter table("events") do + add :organizer_group_id, :integer + end + + rename table("members"), :actor_id, to: :account_id + + alter table("members") do + add :group_id, :integer + remove :parent_id + end + + create table("groups") + rename table("actors"), to: table("accounts") + rename table("accounts"), :name, to: :username + rename table("accounts"), :summary, to: :description + rename table("accounts"), :preferred_username, to: :display_name + alter table("accounts") do + remove :inbox_url + remove :outbox_url + remove :following_url + remove :followers_url + remove :shared_inbox_url + remove :type + remove :manually_approves_followers + modify :username, :string, null: false + modify :display_name, :string, null: true + end + Eventos.Actors.ActorTypeEnum.drop_type() + + rename table("events"), :organizer_actor_id, to: :organizer_account_id + + rename table("participants"), :actor_id, to: :account_id + + rename table("comments"), :actor_id, to: :account_id + + drop index("accounts", [:preferred_username, :domain], name: :actors_preferred_username_domain_index) + + drop table("followers") + end +end