WIP
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
1a3fbcf7f7
commit
416fd1e433
@ -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
167
lib/eventos/actors/actor.ex
Normal 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
|
444
lib/eventos/actors/actors.ex
Normal file
444
lib/eventos/actors/actors.ex
Normal 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
|
26
lib/eventos/actors/follower.ex
Normal file
26
lib/eventos/actors/follower.ex
Normal 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
|
@ -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
|
@ -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/@/)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
155
lib/eventos_web/views/activity_pub/actor_view.ex
Normal file
155
lib/eventos_web/views/activity_pub/actor_view.ex
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"]
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
1
mix.exs
1
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},
|
||||
|
1
mix.lock
1
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"},
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user