Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2018-05-18 09:56:21 +02:00
parent e14007bac5
commit f1cb601b46
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
39 changed files with 1289 additions and 1028 deletions

View file

@ -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

167
lib/eventos/actors/actor.ex Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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/@/)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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(),

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"]
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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} ->
{: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
_ ->
Logger.debug("Could not validate, re-fetching user and trying one more time")
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
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",
[
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(",")
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
end
end
end

View file

@ -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:)?(?<username>\w+)@#{host}/
regex = ~r/(acct:)?(?<name>\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

View file

@ -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},

View file

@ -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"},

View file

@ -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