forked from AkkomaGang/akkoma
Merge branch 'develop' into csaurus/pleroma-feature/mstdn-direct-api
This commit is contained in:
commit
4b3ec53514
54 changed files with 1456 additions and 169 deletions
|
@ -56,6 +56,8 @@
|
||||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||||
public: true
|
public: true
|
||||||
|
|
||||||
|
config :pleroma, :activitypub, accept_blocks: true
|
||||||
|
|
||||||
config :pleroma, :mrf_simple,
|
config :pleroma, :mrf_simple,
|
||||||
media_removal: [],
|
media_removal: [],
|
||||||
media_nsfw: [],
|
media_nsfw: [],
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
pool: Ecto.Adapters.SQL.Sandbox
|
pool: Ecto.Adapters.SQL.Sandbox
|
||||||
|
|
||||||
# Reduce hash rounds for testing
|
# Reduce hash rounds for testing
|
||||||
config :comeonin, :pbkdf2_rounds, 1
|
config :pbkdf2_elixir, rounds: 1
|
||||||
|
|
||||||
config :pleroma, :websub, Pleroma.Web.WebsubMock
|
config :pleroma, :websub, Pleroma.Web.WebsubMock
|
||||||
config :pleroma, :ostatus, Pleroma.Web.OStatusMock
|
config :pleroma, :ostatus, Pleroma.Web.OStatusMock
|
||||||
|
|
|
@ -52,8 +52,9 @@ server {
|
||||||
# if you do not want remote frontends to be able to access your Pleroma backend
|
# if you do not want remote frontends to be able to access your Pleroma backend
|
||||||
# server, remove these lines.
|
# server, remove these lines.
|
||||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS' always;
|
add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
|
||||||
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
|
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
|
||||||
|
add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
|
||||||
if ($request_method = OPTIONS) {
|
if ($request_method = OPTIONS) {
|
||||||
return 204;
|
return 204;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Application do
|
||||||
# for more information on OTP Applications
|
# for more information on OTP Applications
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
import Supervisor.Spec
|
import Supervisor.Spec
|
||||||
|
import Cachex.Spec
|
||||||
|
|
||||||
# Define workers and child supervisors to be supervised
|
# Define workers and child supervisors to be supervised
|
||||||
children =
|
children =
|
||||||
|
@ -28,8 +29,11 @@ def start(_type, _args) do
|
||||||
[
|
[
|
||||||
:idempotency_cache,
|
:idempotency_cache,
|
||||||
[
|
[
|
||||||
default_ttl: :timer.seconds(6 * 60 * 60),
|
expiration:
|
||||||
ttl_interval: :timer.seconds(60),
|
expiration(
|
||||||
|
default: :timer.seconds(6 * 60 * 60),
|
||||||
|
interval: :timer.seconds(60)
|
||||||
|
),
|
||||||
limit: 2500
|
limit: 2500
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
87
lib/pleroma/list.ex
Normal file
87
lib/pleroma/list.ex
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
defmodule Pleroma.List do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.{Changeset, Query}
|
||||||
|
alias Pleroma.{User, Repo}
|
||||||
|
|
||||||
|
schema "lists" do
|
||||||
|
belongs_to(:user, Pleroma.User)
|
||||||
|
field(:title, :string)
|
||||||
|
field(:following, {:array, :string}, default: [])
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def title_changeset(list, attrs \\ %{}) do
|
||||||
|
list
|
||||||
|
|> cast(attrs, [:title])
|
||||||
|
|> validate_required([:title])
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_changeset(list, attrs \\ %{}) do
|
||||||
|
list
|
||||||
|
|> cast(attrs, [:following])
|
||||||
|
|> validate_required([:following])
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user(user, opts) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
l in Pleroma.List,
|
||||||
|
where: l.user_id == ^user.id,
|
||||||
|
order_by: [desc: l.id],
|
||||||
|
limit: 50
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(id, %{id: user_id} = _user) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
l in Pleroma.List,
|
||||||
|
where: l.id == ^id,
|
||||||
|
where: l.user_id == ^user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.one(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_following(%Pleroma.List{following: following} = list) do
|
||||||
|
q =
|
||||||
|
from(
|
||||||
|
u in User,
|
||||||
|
where: u.follower_address in ^following
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, Repo.all(q)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def rename(%Pleroma.List{} = list, title) do
|
||||||
|
list
|
||||||
|
|> title_changeset(%{title: title})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(title, %User{} = creator) do
|
||||||
|
list = %Pleroma.List{user_id: creator.id, title: title}
|
||||||
|
Repo.insert(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
|
||||||
|
update_follows(list, %{following: Enum.uniq([followed.follower_address | following])})
|
||||||
|
end
|
||||||
|
|
||||||
|
def unfollow(%Pleroma.List{following: following} = list, %User{} = unfollowed) do
|
||||||
|
update_follows(list, %{following: List.delete(following, unfollowed.follower_address)})
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(%Pleroma.List{} = list) do
|
||||||
|
Repo.delete(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_follows(%Pleroma.List{} = list, attrs) do
|
||||||
|
list
|
||||||
|
|> follow_changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
end
|
|
@ -33,19 +33,15 @@ def get_cached_by_ap_id(ap_id) do
|
||||||
else
|
else
|
||||||
key = "object:#{ap_id}"
|
key = "object:#{ap_id}"
|
||||||
|
|
||||||
Cachex.get!(
|
Cachex.fetch!(:user_cache, key, fn _ ->
|
||||||
:user_cache,
|
object = get_by_ap_id(ap_id)
|
||||||
key,
|
|
||||||
fallback: fn _ ->
|
|
||||||
object = get_by_ap_id(ap_id)
|
|
||||||
|
|
||||||
if object do
|
if object do
|
||||||
{:commit, object}
|
{:commit, object}
|
||||||
else
|
else
|
||||||
{:ignore, object}
|
{:ignore, object}
|
||||||
end
|
|
||||||
end
|
end
|
||||||
)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _opts) do
|
def call(conn, _opts) do
|
||||||
user = Utils.normalize_actor(conn.params["actor"])
|
user = Utils.get_ap_id(conn.params["actor"])
|
||||||
Logger.debug("Checking sig for #{user}")
|
Logger.debug("Checking sig for #{user}")
|
||||||
[signature | _] = get_req_header(conn, "signature")
|
[signature | _] = get_req_header(conn, "signature")
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,8 @@ def user_info(%User{} = user) do
|
||||||
%{
|
%{
|
||||||
following_count: length(user.following) - oneself,
|
following_count: length(user.following) - oneself,
|
||||||
note_count: user.info["note_count"] || 0,
|
note_count: user.info["note_count"] || 0,
|
||||||
follower_count: user.info["follower_count"] || 0
|
follower_count: user.info["follower_count"] || 0,
|
||||||
|
locked: user.info["locked"] || false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -167,28 +168,62 @@ def register_changeset(struct, params \\ %{}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
|
||||||
|
user_info = user_info(followed)
|
||||||
|
|
||||||
|
should_direct_follow =
|
||||||
|
cond do
|
||||||
|
# if the account is locked, don't pre-create the relationship
|
||||||
|
user_info["locked"] == true ->
|
||||||
|
false
|
||||||
|
|
||||||
|
# if the users are blocking each other, we shouldn't even be here, but check for it anyway
|
||||||
|
User.blocks?(follower, followed) == true or User.blocks?(followed, follower) == true ->
|
||||||
|
false
|
||||||
|
|
||||||
|
# if OStatus, then there is no three-way handshake to follow
|
||||||
|
User.ap_enabled?(followed) != true ->
|
||||||
|
true
|
||||||
|
|
||||||
|
# if there are no other reasons not to, just pre-create the relationship
|
||||||
|
true ->
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
if should_direct_follow do
|
||||||
|
follow(follower, followed)
|
||||||
|
else
|
||||||
|
follower
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def follow(%User{} = follower, %User{info: info} = followed) do
|
def follow(%User{} = follower, %User{info: info} = followed) do
|
||||||
ap_followers = followed.follower_address
|
ap_followers = followed.follower_address
|
||||||
|
|
||||||
if following?(follower, followed) or info["deactivated"] do
|
cond do
|
||||||
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
|
following?(follower, followed) or info["deactivated"] ->
|
||||||
else
|
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
|
||||||
if !followed.local && follower.local && !ap_enabled?(followed) do
|
|
||||||
Websub.subscribe(follower, followed)
|
|
||||||
end
|
|
||||||
|
|
||||||
following =
|
blocks?(followed, follower) ->
|
||||||
[ap_followers | follower.following]
|
{:error, "Could not follow user: #{followed.nickname} blocked you."}
|
||||||
|> Enum.uniq()
|
|
||||||
|
true ->
|
||||||
|
if !followed.local && follower.local && !ap_enabled?(followed) do
|
||||||
|
Websub.subscribe(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
following =
|
||||||
|
[ap_followers | follower.following]
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
follower =
|
||||||
|
follower
|
||||||
|
|> follow_changeset(%{following: following})
|
||||||
|
|> update_and_set_cache
|
||||||
|
|
||||||
|
{:ok, _} = update_follower_count(followed)
|
||||||
|
|
||||||
follower =
|
|
||||||
follower
|
follower
|
||||||
|> follow_changeset(%{following: following})
|
|
||||||
|> update_and_set_cache
|
|
||||||
|
|
||||||
{:ok, _} = update_follower_count(followed)
|
|
||||||
|
|
||||||
follower
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -223,9 +258,9 @@ def get_by_ap_id(ap_id) do
|
||||||
|
|
||||||
def update_and_set_cache(changeset) do
|
def update_and_set_cache(changeset) do
|
||||||
with {:ok, user} <- Repo.update(changeset) do
|
with {:ok, user} <- Repo.update(changeset) do
|
||||||
Cachex.set(:user_cache, "ap_id:#{user.ap_id}", user)
|
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||||
Cachex.set(:user_cache, "nickname:#{user.nickname}", user)
|
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
|
||||||
Cachex.set(:user_cache, "user_info:#{user.id}", user_info(user))
|
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
e -> e
|
e -> e
|
||||||
|
@ -239,12 +274,12 @@ def invalidate_cache(user) do
|
||||||
|
|
||||||
def get_cached_by_ap_id(ap_id) do
|
def get_cached_by_ap_id(ap_id) do
|
||||||
key = "ap_id:#{ap_id}"
|
key = "ap_id:#{ap_id}"
|
||||||
Cachex.get!(:user_cache, key, fallback: fn _ -> get_by_ap_id(ap_id) end)
|
Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_by_nickname(nickname) do
|
def get_cached_by_nickname(nickname) do
|
||||||
key = "nickname:#{nickname}"
|
key = "nickname:#{nickname}"
|
||||||
Cachex.get!(:user_cache, key, fallback: fn _ -> get_or_fetch_by_nickname(nickname) end)
|
Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_nickname(nickname) do
|
def get_by_nickname(nickname) do
|
||||||
|
@ -260,7 +295,7 @@ def get_by_nickname_or_email(nickname_or_email) do
|
||||||
|
|
||||||
def get_cached_user_info(user) do
|
def get_cached_user_info(user) do
|
||||||
key = "user_info:#{user.id}"
|
key = "user_info:#{user.id}"
|
||||||
Cachex.get!(:user_cache, key, fallback: fn _ -> user_info(user) end)
|
Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_by_nickname(nickname) do
|
def fetch_by_nickname(nickname) do
|
||||||
|
|
|
@ -104,6 +104,17 @@ def accept(%{to: to, actor: actor, object: object} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reject(%{to: to, actor: actor, object: object} = params) do
|
||||||
|
# only accept false as false value
|
||||||
|
local = !(params[:local] == false)
|
||||||
|
|
||||||
|
with data <- %{"to" => to, "type" => "Reject", "actor" => actor, "object" => object},
|
||||||
|
{:ok, activity} <- insert(data, local),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
||||||
# only accept false as false value
|
# only accept false as false value
|
||||||
local = !(params[:local] == false)
|
local = !(params[:local] == false)
|
||||||
|
@ -201,12 +212,11 @@ def follow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow(follower, followed, local \\ true) do
|
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||||
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
|
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
|
||||||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity),
|
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
|
||||||
{:ok, activity} <- insert(unfollow_data, local),
|
{:ok, activity} <- insert(unfollow_data, local),
|
||||||
:ok,
|
:ok <- maybe_federate(activity) do
|
||||||
maybe_federate(activity) do
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -230,6 +240,29 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
||||||
|
follow_activity = fetch_latest_follow(blocker, blocked)
|
||||||
|
|
||||||
|
if follow_activity do
|
||||||
|
unfollow(blocker, blocked, nil, local)
|
||||||
|
end
|
||||||
|
|
||||||
|
with block_data <- make_block_data(blocker, blocked, activity_id),
|
||||||
|
{:ok, activity} <- insert(block_data, local),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
|
||||||
|
with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
|
||||||
|
unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
|
||||||
|
{:ok, activity} <- insert(unblock_data, local),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
|
@ -476,6 +509,7 @@ def user_data_from_user_object(data) do
|
||||||
"url" => [%{"href" => data["image"]["url"]}]
|
"url" => [%{"href" => data["image"]["url"]}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locked = data["manuallyApprovesFollowers"] || false
|
||||||
data = Transmogrifier.maybe_fix_user_object(data)
|
data = Transmogrifier.maybe_fix_user_object(data)
|
||||||
|
|
||||||
user_data = %{
|
user_data = %{
|
||||||
|
@ -483,7 +517,8 @@ def user_data_from_user_object(data) do
|
||||||
info: %{
|
info: %{
|
||||||
"ap_enabled" => true,
|
"ap_enabled" => true,
|
||||||
"source_data" => data,
|
"source_data" => data,
|
||||||
"banner" => banner
|
"banner" => banner,
|
||||||
|
"locked" => locked
|
||||||
},
|
},
|
||||||
avatar: avatar,
|
avatar: avatar,
|
||||||
nickname: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}",
|
nickname: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}",
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
@ -145,6 +146,78 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
|
||||||
|
with true <- id =~ "follows",
|
||||||
|
%User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
|
||||||
|
%Activity{} = activity <- Utils.fetch_latest_follow(follower, followed) do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
_ -> {:error, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp mastodon_follow_hack(_), do: {:error, nil}
|
||||||
|
|
||||||
|
defp get_follow_activity(follow_object, followed) do
|
||||||
|
with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object),
|
||||||
|
{_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
# Can't find the activity. This might a Mastodon 2.3 "Accept"
|
||||||
|
{:activity, nil} ->
|
||||||
|
mastodon_follow_hack(follow_object, followed)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{"type" => "Accept", "object" => follow_object, "actor" => actor, "id" => id} = data
|
||||||
|
) do
|
||||||
|
with %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||||
|
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||||
|
{:ok, activity} <-
|
||||||
|
ActivityPub.accept(%{
|
||||||
|
to: follow_activity.data["to"],
|
||||||
|
type: "Accept",
|
||||||
|
actor: followed.ap_id,
|
||||||
|
object: follow_activity.data["id"],
|
||||||
|
local: false
|
||||||
|
}) do
|
||||||
|
if not User.following?(follower, followed) do
|
||||||
|
{:ok, follower} = User.follow(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
_e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{"type" => "Reject", "object" => follow_object, "actor" => actor, "id" => id} = data
|
||||||
|
) do
|
||||||
|
with %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||||
|
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||||
|
{:ok, activity} <-
|
||||||
|
ActivityPub.accept(%{
|
||||||
|
to: follow_activity.data["to"],
|
||||||
|
type: "Accept",
|
||||||
|
actor: followed.ap_id,
|
||||||
|
object: follow_activity.data["id"],
|
||||||
|
local: false
|
||||||
|
}) do
|
||||||
|
User.unfollow(follower, followed)
|
||||||
|
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
_e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data
|
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data
|
||||||
) do
|
) do
|
||||||
|
@ -207,11 +280,7 @@ def handle_incoming(
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data
|
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data
|
||||||
) do
|
) do
|
||||||
object_id =
|
object_id = Utils.get_ap_id(object_id)
|
||||||
case object_id do
|
|
||||||
%{"id" => id} -> id
|
|
||||||
id -> id
|
|
||||||
end
|
|
||||||
|
|
||||||
with %User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
|
with %User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:ok, object} <-
|
{:ok, object} <-
|
||||||
|
@ -229,13 +298,68 @@ def handle_incoming(
|
||||||
"object" => %{"type" => "Announce", "object" => object_id},
|
"object" => %{"type" => "Announce", "object" => object_id},
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = data
|
} = _data
|
||||||
) do
|
) do
|
||||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:ok, object} <-
|
{:ok, object} <-
|
||||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||||
{:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
{:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
_e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{
|
||||||
|
"type" => "Undo",
|
||||||
|
"object" => %{"type" => "Follow", "object" => followed},
|
||||||
|
"actor" => follower,
|
||||||
|
"id" => id
|
||||||
|
} = _data
|
||||||
|
) do
|
||||||
|
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||||
|
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||||
|
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
|
||||||
|
User.unfollow(follower, followed)
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@ap_config Application.get_env(:pleroma, :activitypub)
|
||||||
|
@accept_blocks Keyword.get(@ap_config, :accept_blocks)
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{
|
||||||
|
"type" => "Undo",
|
||||||
|
"object" => %{"type" => "Block", "object" => blocked},
|
||||||
|
"actor" => blocker,
|
||||||
|
"id" => id
|
||||||
|
} = _data
|
||||||
|
) do
|
||||||
|
with true <- @accept_blocks,
|
||||||
|
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||||
|
%User{} = blocker <- User.get_or_fetch_by_ap_id(blocker),
|
||||||
|
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||||
|
User.unblock(blocker, blocked)
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = data
|
||||||
|
) do
|
||||||
|
with true <- @accept_blocks,
|
||||||
|
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||||
|
%User{} = blocker = User.get_or_fetch_by_ap_id(blocker),
|
||||||
|
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||||
|
User.unfollow(blocker, blocked)
|
||||||
|
User.block(blocker, blocked)
|
||||||
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
e -> :error
|
e -> :error
|
||||||
end
|
end
|
||||||
|
@ -247,7 +371,7 @@ def handle_incoming(
|
||||||
"object" => %{"type" => "Like", "object" => object_id},
|
"object" => %{"type" => "Like", "object" => object_id},
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = data
|
} = _data
|
||||||
) do
|
) do
|
||||||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:ok, object} <-
|
{:ok, object} <-
|
||||||
|
@ -255,14 +379,10 @@ def handle_incoming(
|
||||||
{:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
{:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
e -> :error
|
_e -> :error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO
|
|
||||||
# Accept
|
|
||||||
# Undo for non-Announce
|
|
||||||
|
|
||||||
def handle_incoming(_), do: :error
|
def handle_incoming(_), do: :error
|
||||||
|
|
||||||
def get_obj_helper(id) do
|
def get_obj_helper(id) do
|
||||||
|
@ -516,10 +636,10 @@ def maybe_retire_websub(ap_id) do
|
||||||
|
|
||||||
def maybe_fix_user_url(data) do
|
def maybe_fix_user_url(data) do
|
||||||
if is_map(data["url"]) do
|
if is_map(data["url"]) do
|
||||||
data = Map.put(data, "url", data["url"]["href"])
|
Map.put(data, "url", data["url"]["href"])
|
||||||
|
else
|
||||||
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
data
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_fix_user_object(data) do
|
def maybe_fix_user_object(data) do
|
||||||
|
|
|
@ -7,18 +7,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||||
|
|
||||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||||
# so figure out what the actor's URI is based on what we have.
|
# so figure out what the actor's URI is based on what we have.
|
||||||
def normalize_actor(actor) do
|
def get_ap_id(object) do
|
||||||
cond do
|
case object do
|
||||||
is_binary(actor) ->
|
%{"id" => id} -> id
|
||||||
actor
|
id -> id
|
||||||
|
|
||||||
is_map(actor) ->
|
|
||||||
actor["id"]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_params(params) do
|
def normalize_params(params) do
|
||||||
Map.put(params, "actor", normalize_actor(params["actor"]))
|
Map.put(params, "actor", get_ap_id(params["actor"]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_json_ld_header do
|
def make_json_ld_header do
|
||||||
|
@ -238,11 +235,17 @@ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
activity in Activity,
|
activity in Activity,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"? ->> 'type' = 'Follow'",
|
||||||
|
activity.data
|
||||||
|
),
|
||||||
|
where: activity.actor == ^follower_id,
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"? @> ?",
|
"? @> ?",
|
||||||
activity.data,
|
activity.data,
|
||||||
^%{type: "Follow", actor: follower_id, object: followed_id}
|
^%{object: followed_id}
|
||||||
),
|
),
|
||||||
order_by: [desc: :id],
|
order_by: [desc: :id],
|
||||||
limit: 1
|
limit: 1
|
||||||
|
@ -260,7 +263,7 @@ def get_existing_announce(actor, %{data: %{"id" => id}}) do
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
activity in Activity,
|
activity in Activity,
|
||||||
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
where: activity.actor == ^actor,
|
||||||
# this is to use the index
|
# this is to use the index
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
|
@ -346,13 +349,61 @@ def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do
|
||||||
|
|
||||||
#### Unfollow-related helpers
|
#### Unfollow-related helpers
|
||||||
|
|
||||||
def make_unfollow_data(follower, followed, follow_activity) do
|
def make_unfollow_data(follower, followed, follow_activity, activity_id) do
|
||||||
%{
|
data = %{
|
||||||
"type" => "Undo",
|
"type" => "Undo",
|
||||||
"actor" => follower.ap_id,
|
"actor" => follower.ap_id,
|
||||||
"to" => [followed.ap_id],
|
"to" => [followed.ap_id],
|
||||||
"object" => follow_activity.data["id"]
|
"object" => follow_activity.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
|
end
|
||||||
|
|
||||||
|
#### Block-related helpers
|
||||||
|
def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
activity in Activity,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"? ->> 'type' = 'Block'",
|
||||||
|
activity.data
|
||||||
|
),
|
||||||
|
where: activity.actor == ^blocker_id,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"? @> ?",
|
||||||
|
activity.data,
|
||||||
|
^%{object: blocked_id}
|
||||||
|
),
|
||||||
|
order_by: [desc: :id],
|
||||||
|
limit: 1
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.one(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_block_data(blocker, blocked, activity_id) do
|
||||||
|
data = %{
|
||||||
|
"type" => "Block",
|
||||||
|
"actor" => blocker.ap_id,
|
||||||
|
"to" => [blocked.ap_id],
|
||||||
|
"object" => blocked.ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_unblock_data(blocker, blocked, block_activity, activity_id) do
|
||||||
|
data = %{
|
||||||
|
"type" => "Undo",
|
||||||
|
"actor" => blocker.ap_id,
|
||||||
|
"to" => [blocked.ap_id],
|
||||||
|
"object" => block_activity.data
|
||||||
|
}
|
||||||
|
|
||||||
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
end
|
end
|
||||||
|
|
||||||
#### Create-related helpers
|
#### Create-related helpers
|
||||||
|
|
|
@ -26,7 +26,7 @@ def render("user.json", %{user: user}) do
|
||||||
"name" => user.name,
|
"name" => user.name,
|
||||||
"summary" => user.bio,
|
"summary" => user.bio,
|
||||||
"url" => user.ap_id,
|
"url" => user.ap_id,
|
||||||
"manuallyApprovesFollowers" => false,
|
"manuallyApprovesFollowers" => user.info["locked"] || false,
|
||||||
"publicKey" => %{
|
"publicKey" => %{
|
||||||
"id" => "#{user.ap_id}#main-key",
|
"id" => "#{user.ap_id}#main-key",
|
||||||
"owner" => user.ap_id,
|
"owner" => user.ap_id,
|
||||||
|
|
|
@ -133,7 +133,7 @@ def make_note_data(
|
||||||
"context" => context,
|
"context" => context,
|
||||||
"attachment" => attachments,
|
"attachment" => attachments,
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"tag" => tags |> Enum.map(fn {_, tag} -> tag end)
|
"tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|
|
||||||
if inReplyTo do
|
if inReplyTo do
|
||||||
|
@ -187,9 +187,9 @@ defp shortname(name) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_current_password(user, params) do
|
def confirm_current_password(user, password) do
|
||||||
with %User{local: true} = db_user <- Repo.get(User, user.id),
|
with %User{local: true} = db_user <- Repo.get(User, user.id),
|
||||||
true <- Pbkdf2.checkpw(params["password"], db_user.password_hash) do
|
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
||||||
{:ok, db_user}
|
{:ok, db_user}
|
||||||
else
|
else
|
||||||
_ -> {:error, "Invalid password."}
|
_ -> {:error, "Invalid password."}
|
||||||
|
|
|
@ -32,14 +32,14 @@ def validate(headers, signature, public_key) do
|
||||||
def validate_conn(conn) do
|
def validate_conn(conn) do
|
||||||
# TODO: How to get the right key and see if it is actually valid for that request.
|
# 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.
|
# For now, fetch the key for the actor.
|
||||||
with actor_id <- Utils.normalize_actor(conn.params["actor"]),
|
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
if validate_conn(conn, public_key) do
|
if validate_conn(conn, public_key) do
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
Logger.debug("Could not validate, re-fetching user and trying one more time")
|
Logger.debug("Could not validate, re-fetching user and trying one more time")
|
||||||
# Fetch user anew and try one more time
|
# Fetch user anew and try one more time
|
||||||
with actor_id <- Utils.normalize_actor(conn.params["actor"]),
|
with actor_id <- Utils.get_ap_id(conn.params["actor"]),
|
||||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
validate_conn(conn, public_key)
|
validate_conn(conn, public_key)
|
||||||
|
|
|
@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
alias Pleroma.{Repo, Activity, User, Notification, Stats}
|
alias Pleroma.{Repo, Activity, User, Notification, Stats}
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
|
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.{CommonAPI, OStatus}
|
alias Pleroma.Web.{CommonAPI, OStatus}
|
||||||
alias Pleroma.Web.OAuth.{Authorization, Token, App}
|
alias Pleroma.Web.OAuth.{Authorization, Token, App}
|
||||||
|
@ -284,11 +284,7 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
Cachex.get!(
|
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
||||||
:idempotency_cache,
|
|
||||||
idempotency_key,
|
|
||||||
fallback: fn _ -> CommonAPI.post(user, params) end
|
|
||||||
)
|
|
||||||
|
|
||||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
@ -442,7 +438,7 @@ def following(conn, %{"id" => id}) do
|
||||||
|
|
||||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with %User{} = followed <- Repo.get(User, id),
|
with %User{} = followed <- Repo.get(User, id),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||||
else
|
else
|
||||||
|
@ -455,7 +451,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
|
|
||||||
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
|
||||||
render(conn, AccountView, "account.json", %{user: followed})
|
render(conn, AccountView, "account.json", %{user: followed})
|
||||||
else
|
else
|
||||||
|
@ -466,24 +462,18 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Clean up and unify
|
|
||||||
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with %User{} = followed <- Repo.get(User, id),
|
with %User{} = followed <- Repo.get(User, id),
|
||||||
{:ok, follower, follow_activity} <- User.unfollow(follower, followed),
|
{:ok, _activity} <- ActivityPub.unfollow(follower, followed),
|
||||||
{:ok, _activity} <-
|
{:ok, follower, _} <- User.unfollow(follower, followed) do
|
||||||
ActivityPub.insert(%{
|
|
||||||
"type" => "Undo",
|
|
||||||
"actor" => follower.ap_id,
|
|
||||||
# get latest Follow for these users
|
|
||||||
"object" => follow_activity.data["id"]
|
|
||||||
}) do
|
|
||||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- Repo.get(User, id),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
{:ok, blocker} <- User.block(blocker, blocked),
|
||||||
|
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||||
render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked})
|
render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked})
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
|
@ -495,7 +485,8 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
|
|
||||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- Repo.get(User, id),
|
||||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
{:ok, blocker} <- User.unblock(blocker, blocked),
|
||||||
|
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
||||||
render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked})
|
render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked})
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
|
@ -586,6 +577,102 @@ def favourites(%{assigns: %{user: user}} = conn, _) do
|
||||||
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_lists(%{assigns: %{user: user}} = conn, opts) do
|
||||||
|
lists = Pleroma.List.for_user(user, opts)
|
||||||
|
res = ListView.render("lists.json", lists: lists)
|
||||||
|
json(conn, res)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user) do
|
||||||
|
res = ListView.render("list.json", list: list)
|
||||||
|
json(conn, res)
|
||||||
|
else
|
||||||
|
_e -> json(conn, "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
|
{:ok, _list} <- Pleroma.List.delete(list) do
|
||||||
|
json(conn, %{})
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
json(conn, "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
|
||||||
|
with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
|
||||||
|
res = ListView.render("list.json", list: list)
|
||||||
|
json(conn, res)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
|
||||||
|
accounts
|
||||||
|
|> Enum.each(fn account_id ->
|
||||||
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
|
%User{} = followed <- Repo.get(User, account_id) do
|
||||||
|
Pleroma.List.follow(list, followed)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
|
||||||
|
accounts
|
||||||
|
|> Enum.each(fn account_id ->
|
||||||
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
|
%User{} = followed <- Repo.get(Pleroma.User, account_id) do
|
||||||
|
Pleroma.List.unfollow(list, followed)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
|
{:ok, users} = Pleroma.List.get_following(list) do
|
||||||
|
render(conn, AccountView, "accounts.json", %{users: users, as: :user})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
|
||||||
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
|
{:ok, list} <- Pleroma.List.rename(list, title) do
|
||||||
|
res = ListView.render("list.json", list: list)
|
||||||
|
json(conn, res)
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
json(conn, "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|
||||||
|
with %Pleroma.List{title: title, following: following} <- Pleroma.List.get(id, user) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|
||||||
|
# adding title is a hack to not make empty lists function like a public timeline
|
||||||
|
activities =
|
||||||
|
ActivityPub.fetch_activities([title | following], params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
conn
|
||||||
|
|> put_status(403)
|
||||||
|
|> json(%{error: "Error."})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
token =
|
token =
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -19,7 +19,7 @@ def render("account.json", %{user: user}) do
|
||||||
username: hd(String.split(user.nickname, "@")),
|
username: hd(String.split(user.nickname, "@")),
|
||||||
acct: user.nickname,
|
acct: user.nickname,
|
||||||
display_name: user.name || user.nickname,
|
display_name: user.name || user.nickname,
|
||||||
locked: false,
|
locked: user_info.locked,
|
||||||
created_at: Utils.to_masto_date(user.inserted_at),
|
created_at: Utils.to_masto_date(user.inserted_at),
|
||||||
followers_count: user_info.follower_count,
|
followers_count: user_info.follower_count,
|
||||||
following_count: user_info.following_count,
|
following_count: user_info.following_count,
|
||||||
|
|
15
lib/pleroma/web/mastodon_api/views/list_view.ex
Normal file
15
lib/pleroma/web/mastodon_api/views/list_view.ex
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ListView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
alias Pleroma.Web.MastodonAPI.ListView
|
||||||
|
|
||||||
|
def render("lists.json", %{lists: lists} = opts) do
|
||||||
|
render_many(lists, ListView, "list.json", opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("list.json", %{list: list}) do
|
||||||
|
%{
|
||||||
|
id: to_string(list.id),
|
||||||
|
title: list.title
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -232,7 +232,12 @@ def to_simple_form(%{data: %{"type" => "Follow"}} = activity, user, with_author)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only undos of follow for now. Will need to get redone once there are more
|
# Only undos of follow for now. Will need to get redone once there are more
|
||||||
def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) do
|
def to_simple_form(
|
||||||
|
%{data: %{"type" => "Undo", "object" => %{"type" => "Follow"} = follow_activity}} =
|
||||||
|
activity,
|
||||||
|
user,
|
||||||
|
with_author
|
||||||
|
) do
|
||||||
h = fn str -> [to_charlist(str)] end
|
h = fn str -> [to_charlist(str)] end
|
||||||
|
|
||||||
updated_at = activity.data["published"]
|
updated_at = activity.data["published"]
|
||||||
|
@ -240,34 +245,26 @@ def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) d
|
||||||
|
|
||||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||||
|
|
||||||
follow_activity =
|
|
||||||
if is_map(activity.data["object"]) do
|
|
||||||
Activity.get_by_ap_id(activity.data["object"]["id"])
|
|
||||||
else
|
|
||||||
Activity.get_by_ap_id(activity.data["object"])
|
|
||||||
end
|
|
||||||
|
|
||||||
mentions = (activity.recipients || []) |> get_mentions
|
mentions = (activity.recipients || []) |> get_mentions
|
||||||
|
follow_activity = Activity.get_by_ap_id(follow_activity["id"])
|
||||||
|
|
||||||
if follow_activity do
|
[
|
||||||
[
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
{:id, h.(activity.data["id"])},
|
||||||
{:id, h.(activity.data["id"])},
|
{:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||||
{:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
{:content, [type: 'html'],
|
||||||
{:content, [type: 'html'],
|
['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||||
['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
{:published, h.(inserted_at)},
|
||||||
{:published, h.(inserted_at)},
|
{:updated, h.(updated_at)},
|
||||||
{:updated, h.(updated_at)},
|
{:"activity:object",
|
||||||
{:"activity:object",
|
[
|
||||||
[
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
{:id, h.(follow_activity.data["object"])},
|
||||||
{:id, h.(follow_activity.data["object"])},
|
{:uri, h.(follow_activity.data["object"])}
|
||||||
{:uri, h.(follow_activity.data["object"])}
|
]},
|
||||||
]},
|
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
||||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
] ++ mentions ++ author
|
||||||
] ++ mentions ++ author
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
|
def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
|
||||||
|
|
17
lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
Normal file
17
lib/pleroma/web/ostatus/handlers/unfollow_handler.ex
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule Pleroma.Web.OStatus.UnfollowHandler do
|
||||||
|
alias Pleroma.Web.{XML, OStatus}
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def handle(entry, doc) do
|
||||||
|
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
|
||||||
|
id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry),
|
||||||
|
followed_uri when not is_nil(followed_uri) <-
|
||||||
|
XML.string_from_xpath("/entry/activity:object/id", entry),
|
||||||
|
{:ok, followed} <- OStatus.find_or_make_user(followed_uri),
|
||||||
|
{:ok, activity} <- ActivityPub.unfollow(actor, followed, id, false) do
|
||||||
|
User.unfollow(actor, followed)
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.OStatus do
|
||||||
alias Pleroma.{Repo, User, Web, Object, Activity}
|
alias Pleroma.{Repo, User, Web, Object, Activity}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.{WebFinger, Websub}
|
alias Pleroma.Web.{WebFinger, Websub}
|
||||||
alias Pleroma.Web.OStatus.{FollowHandler, NoteHandler, DeleteHandler}
|
alias Pleroma.Web.OStatus.{FollowHandler, UnfollowHandler, NoteHandler, DeleteHandler}
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
def feed_path(user) do
|
def feed_path(user) do
|
||||||
|
@ -47,6 +47,9 @@ def handle_incoming(xml_string) do
|
||||||
'http://activitystrea.ms/schema/1.0/follow' ->
|
'http://activitystrea.ms/schema/1.0/follow' ->
|
||||||
with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
|
with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity
|
||||||
|
|
||||||
|
'http://activitystrea.ms/schema/1.0/unfollow' ->
|
||||||
|
with {:ok, activity} <- UnfollowHandler.handle(entry, doc), do: activity
|
||||||
|
|
||||||
'http://activitystrea.ms/schema/1.0/share' ->
|
'http://activitystrea.ms/schema/1.0/share' ->
|
||||||
with {:ok, activity, retweeted_activity} <- handle_share(entry, doc),
|
with {:ok, activity, retweeted_activity} <- handle_share(entry, doc),
|
||||||
do: [activity, retweeted_activity]
|
do: [activity, retweeted_activity]
|
||||||
|
|
|
@ -73,6 +73,7 @@ def user_fetcher(username) do
|
||||||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||||
pipe_through(:authenticated_api)
|
pipe_through(:authenticated_api)
|
||||||
post("/follow_import", UtilController, :follow_import)
|
post("/follow_import", UtilController, :follow_import)
|
||||||
|
post("/change_password", UtilController, :change_password)
|
||||||
post("/delete_account", UtilController, :delete_account)
|
post("/delete_account", UtilController, :delete_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -103,7 +104,6 @@ def user_fetcher(username) do
|
||||||
get("/domain_blocks", MastodonAPIController, :empty_array)
|
get("/domain_blocks", MastodonAPIController, :empty_array)
|
||||||
get("/follow_requests", MastodonAPIController, :empty_array)
|
get("/follow_requests", MastodonAPIController, :empty_array)
|
||||||
get("/mutes", MastodonAPIController, :empty_array)
|
get("/mutes", MastodonAPIController, :empty_array)
|
||||||
get("/lists", MastodonAPIController, :empty_array)
|
|
||||||
|
|
||||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
get("/timelines/home", MastodonAPIController, :home_timeline)
|
||||||
|
|
||||||
|
@ -125,6 +125,15 @@ def user_fetcher(username) do
|
||||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||||
|
|
||||||
post("/media", MastodonAPIController, :upload)
|
post("/media", MastodonAPIController, :upload)
|
||||||
|
|
||||||
|
get("/lists", MastodonAPIController, :get_lists)
|
||||||
|
get("/lists/:id", MastodonAPIController, :get_list)
|
||||||
|
delete("/lists/:id", MastodonAPIController, :delete_list)
|
||||||
|
post("/lists", MastodonAPIController, :create_list)
|
||||||
|
put("/lists/:id", MastodonAPIController, :rename_list)
|
||||||
|
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
||||||
|
post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
|
||||||
|
delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/web", Pleroma.Web.MastodonAPI do
|
scope "/api/web", Pleroma.Web.MastodonAPI do
|
||||||
|
@ -142,6 +151,7 @@ def user_fetcher(username) do
|
||||||
|
|
||||||
get("/timelines/public", MastodonAPIController, :public_timeline)
|
get("/timelines/public", MastodonAPIController, :public_timeline)
|
||||||
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
||||||
|
get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
|
||||||
|
|
||||||
get("/statuses/:id", MastodonAPIController, :get_status)
|
get("/statuses/:id", MastodonAPIController, :get_status)
|
||||||
get("/statuses/:id/context", MastodonAPIController, :get_context)
|
get("/statuses/:id/context", MastodonAPIController, :get_context)
|
||||||
|
|
|
@ -197,8 +197,31 @@ def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do
|
||||||
json(conn, "job started")
|
json(conn, "job started")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def change_password(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||||
|
{:ok, user} ->
|
||||||
|
with {:ok, _user} <-
|
||||||
|
User.reset_password(user, %{
|
||||||
|
password: params["new_password"],
|
||||||
|
password_confirmation: params["new_password_confirmation"]
|
||||||
|
}) do
|
||||||
|
json(conn, %{status: "success"})
|
||||||
|
else
|
||||||
|
{:error, changeset} ->
|
||||||
|
{_, {error, _}} = Enum.at(changeset.errors, 0)
|
||||||
|
json(conn, %{error: "New password #{error}."})
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
json(conn, %{error: "Unable to change password."})
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, msg} ->
|
||||||
|
json(conn, %{error: msg})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def delete_account(%{assigns: %{user: user}} = conn, params) do
|
def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||||
case CommonAPI.Utils.confirm_current_password(user, params) do
|
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||||
{:ok, user} ->
|
{:ok, user} ->
|
||||||
Task.start(fn -> User.delete(user) end)
|
Task.start(fn -> User.delete(user) end)
|
||||||
json(conn, %{status: "success"})
|
json(conn, %{status: "success"})
|
||||||
|
|
|
@ -99,7 +99,7 @@ def to_map(
|
||||||
) do
|
) do
|
||||||
created_at = created_at |> Utils.date_to_asctime()
|
created_at = created_at |> Utils.date_to_asctime()
|
||||||
|
|
||||||
text = "#{user.nickname} undid the action at #{undid_activity}"
|
text = "#{user.nickname} undid the action at #{undid_activity["id"]}"
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => activity.id,
|
"id" => activity.id,
|
||||||
|
|
|
@ -25,7 +25,7 @@ def delete(%User{} = user, id) do
|
||||||
|
|
||||||
def follow(%User{} = follower, params) do
|
def follow(%User{} = follower, params) do
|
||||||
with {:ok, %User{} = followed} <- get_user(params),
|
with {:ok, %User{} = followed} <- get_user(params),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||||
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
{:ok, activity} <- ActivityPub.follow(follower, followed) do
|
||||||
{:ok, follower, followed, activity}
|
{:ok, follower, followed, activity}
|
||||||
else
|
else
|
||||||
|
@ -36,14 +36,7 @@ def follow(%User{} = follower, params) do
|
||||||
def unfollow(%User{} = follower, params) do
|
def unfollow(%User{} = follower, params) do
|
||||||
with {:ok, %User{} = unfollowed} <- get_user(params),
|
with {:ok, %User{} = unfollowed} <- get_user(params),
|
||||||
{:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed),
|
{:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed),
|
||||||
{:ok, _activity} <-
|
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
|
||||||
ActivityPub.insert(%{
|
|
||||||
"type" => "Undo",
|
|
||||||
"actor" => follower.ap_id,
|
|
||||||
# get latest Follow for these users
|
|
||||||
"object" => follow_activity.data["id"],
|
|
||||||
"published" => make_date()
|
|
||||||
}) do
|
|
||||||
{:ok, follower, unfollowed}
|
{:ok, follower, unfollowed}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
|
@ -52,7 +45,8 @@ def unfollow(%User{} = follower, params) do
|
||||||
|
|
||||||
def block(%User{} = blocker, params) do
|
def block(%User{} = blocker, params) do
|
||||||
with {:ok, %User{} = blocked} <- get_user(params),
|
with {:ok, %User{} = blocked} <- get_user(params),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
{:ok, blocker} <- User.block(blocker, blocked),
|
||||||
|
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||||
{:ok, blocker, blocked}
|
{:ok, blocker, blocked}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
|
@ -61,7 +55,8 @@ def block(%User{} = blocker, params) do
|
||||||
|
|
||||||
def unblock(%User{} = blocker, params) do
|
def unblock(%User{} = blocker, params) do
|
||||||
with {:ok, %User{} = blocked} <- get_user(params),
|
with {:ok, %User{} = blocked} <- get_user(params),
|
||||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
{:ok, blocker} <- User.unblock(blocker, blocked),
|
||||||
|
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
||||||
{:ok, blocker, blocked}
|
{:ok, blocker, blocked}
|
||||||
else
|
else
|
||||||
err -> err
|
err -> err
|
||||||
|
|
5
mix.exs
5
mix.exs
|
@ -36,12 +36,13 @@ defp deps do
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:gettext, "~> 0.11"},
|
{:gettext, "~> 0.11"},
|
||||||
{:cowboy, "~> 1.0", override: true},
|
{:cowboy, "~> 1.0", override: true},
|
||||||
{:comeonin, "~> 3.0"},
|
{:comeonin, "~> 4.0"},
|
||||||
|
{:pbkdf2_elixir, "~> 0.12"},
|
||||||
{:trailing_format_plug, "~> 0.0.5"},
|
{:trailing_format_plug, "~> 0.0.5"},
|
||||||
{:html_sanitize_ex, "~> 1.3.0-rc1"},
|
{:html_sanitize_ex, "~> 1.3.0-rc1"},
|
||||||
{:phoenix_html, "~> 2.10"},
|
{:phoenix_html, "~> 2.10"},
|
||||||
{:calendar, "~> 0.16.1"},
|
{:calendar, "~> 0.16.1"},
|
||||||
{:cachex, "~> 2.1"},
|
{:cachex, "~> 3.0"},
|
||||||
{:httpoison, "~> 1.1.0"},
|
{:httpoison, "~> 1.1.0"},
|
||||||
{:jason, "~> 1.0"},
|
{:jason, "~> 1.0"},
|
||||||
{:ex_machina, "~> 2.0", only: :test},
|
{:ex_machina, "~> 2.0", only: :test},
|
||||||
|
|
20
mix.lock
20
mix.lock
|
@ -1,42 +1,37 @@
|
||||||
%{
|
%{
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||||
"cachex": {:hex, :cachex, "2.1.0", "fad49b4e78d11c6c314e75bd8c9408f5b78cb065c047442798caed10803ee3be", [:mix], [{:eternal, "~> 1.1", [hex: :eternal, repo: "hexpm", optional: false]}], "hexpm"},
|
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"calendar": {:hex, :calendar, "0.16.1", "782327ad8bae7c797b887840dc4ddb933f05ce6e333e5b04964d7a5d5f79bde3", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
"calendar": {:hex, :calendar, "0.16.1", "782327ad8bae7c797b887840dc4ddb933f05ce6e333e5b04964d7a5d5f79bde3", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"comeonin": {:hex, :comeonin, "3.2.0", "cb10995a22aed6812667efb3856f548818c85d85394d8132bc116fbd6995c1ef", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
|
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"con_cache": {:hex, :con_cache, "0.12.0", "2d961aec219aa5a914473873f348f5a6088292dc69d5192a9d25f8a1e13e9905", [:mix], [{:exactor, "~> 2.2.0", [hex: :exactor, optional: false]}]},
|
|
||||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||||
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
|
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
|
||||||
"credo": {:hex, :credo, "0.9.1", "f021affa11b32a94dc2e807a6472ce0914289c9132f99644a97fc84432b202a1", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
|
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
|
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
|
||||||
"deppie": {:hex, :deppie, "1.1.0", "cfb6fcee7dfb64eb78cb8505537971a0805131899326ad469ef10df04520f451", [:mix], []},
|
|
||||||
"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": {: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"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.4.1", "6628b86053190a80b9072382bb9756a6c78624f208ec0ff22cb94c8977d80060", [:mix], [], "hexpm"},
|
|
||||||
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
|
||||||
"ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
"ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"exactor": {:hex, :exactor, "2.2.3", "a6972f43bb6160afeb73e1d8ab45ba604cd0ac8b5244c557093f6e92ce582786", [:mix], []},
|
|
||||||
"fs": {:hex, :fs, "2.12.0", "ad631efacc9a5683c8eaa1b274e24fa64a1b8eb30747e9595b93bec7e492e25e", [:rebar3], []},
|
|
||||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
||||||
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"httpoison": {:hex, :httpoison, "1.1.0", "497949fb62924432f64a45269d20e6f61ecf35084ffa270917afcdb7cd4d8061", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"httpoison": {:hex, :httpoison, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
"idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
"jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"},
|
"meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||||
"mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
|
"mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
|
||||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
||||||
"mix_test_watch": {:hex, :mix_test_watch, "0.3.3", "70859889a8d1d43d1b75d69d87258a301f43209a17787cdb2bd9cab42adf271d", [:mix], [{:fs, "~> 2.12", [hex: :fs, optional: false]}]},
|
|
||||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||||
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
|
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
|
||||||
|
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
|
||||||
"phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "2.11.1", "77b6f7fbd252168c6ec4f573de648d37cc5258cda13266ef001fbf99267eb6f3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"},
|
||||||
"plug": {:hex, :plug, "1.5.0", "224b25b4039bedc1eac149fb52ed456770b9678bbf0349cdd810460e1e09195b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
|
"plug": {:hex, :plug, "1.5.1", "1ff35bdecfb616f1a2b1c935ab5e4c47303f866cb929d2a76f0541e553a58165", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.3", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
|
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
|
||||||
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
|
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
@ -45,4 +40,5 @@
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
|
||||||
|
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||||
}
|
}
|
||||||
|
|
15
priv/repo/migrations/20180429094642_create_lists.exs
Normal file
15
priv/repo/migrations/20180429094642_create_lists.exs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateLists do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:lists) do
|
||||||
|
add :user_id, references(:users, on_delete: :delete_all)
|
||||||
|
add :title, :string
|
||||||
|
add :following, {:array, :string}
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create index(:lists, [:user_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.c0e1e1e1fcff94fd1e14fc44bfee9a1e.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f40706e221610d4c6256.js></script><script type=text/javascript src=/static/js/vendor.56aa9f8c34786f6af6b7.js></script><script type=text/javascript src=/static/js/app.029b23b3921537271958.js></script></body></html>
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.c0e1e1e1fcff94fd1e14fc44bfee9a1e.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.16ab7851cdbf730f9cbc.js></script><script type=text/javascript src=/static/js/vendor.56aa9f8c34786f6af6b7.js></script><script type=text/javascript src=/static/js/app.13c0bda10eb515cdf8ed.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/app.13c0bda10eb515cdf8ed.js
Normal file
BIN
priv/static/static/js/app.13c0bda10eb515cdf8ed.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/app.13c0bda10eb515cdf8ed.js.map
Normal file
BIN
priv/static/static/js/app.13c0bda10eb515cdf8ed.js.map
Normal file
Binary file not shown.
BIN
priv/static/static/js/manifest.16ab7851cdbf730f9cbc.js
Normal file
BIN
priv/static/static/js/manifest.16ab7851cdbf730f9cbc.js
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
test/fixtures/mastodon-block-activity.json
vendored
Normal file
29
test/fixtures/mastodon-block-activity.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"type": "Block",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "Kn1/UkAQGJVaXBfWLAHcnwHg8YMAUqlEaBuYLazAG+pz5hqivsyrBmPV186Xzr+B4ZLExA9+SnOoNx/GOz4hBm0kAmukNSILAsUd84tcJ2yT9zc1RKtembK4WiwOw7li0+maeDN0HaB6t+6eTqsCWmtiZpprhXD8V1GGT8yG7X24fQ9oFGn+ng7lasbcCC0988Y1eGqNe7KryxcPuQz57YkDapvtONzk8gyLTkZMV4De93MyRHq6GVjQVIgtiYabQAxrX6Q8C+4P/jQoqdWJHEe+MY5JKyNaT/hMPt2Md1ok9fZQBGHlErk22/zy8bSN19GdG09HmIysBUHRYpBLig==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-02-17T13:29:31Z"
|
||||||
|
},
|
||||||
|
"object": "http://localtesting.pleroma.lol/users/lain",
|
||||||
|
"nickname": "lain",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#follows/2",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
34
test/fixtures/mastodon-reject-activity.json
vendored
Normal file
34
test/fixtures/mastodon-reject-activity.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"type": "Reject",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "rBzK4Kqhd4g7HDS8WE5oRbWQb2R+HF/6awbUuMWhgru/xCODT0SJWSri0qWqEO4fPcpoUyz2d25cw6o+iy9wiozQb3hQNnu69AR+H5Mytc06+g10KCHexbGhbAEAw/7IzmeXELHUbaqeduaDIbdt1zw4RkwLXdqgQcGXTJ6ND1wM3WMHXQCK1m0flasIXFoBxpliPAGiElV8s0+Ltuh562GvflG3kB3WO+j+NaR0ZfG5G9N88xMj9UQlCKit5gpAE5p6syUsCU2WGBHywTumv73i3OVTIFfq+P9AdMsRuzw1r7zoKEsthW4aOzLQDi01ZjvdBz8zH6JnjDU7SMN/Ig==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-02-17T14:36:41Z"
|
||||||
|
},
|
||||||
|
"object": {
|
||||||
|
"type": "Follow",
|
||||||
|
"object": "http://mastodon.example.org/users/admin",
|
||||||
|
"id": "http://localtesting.pleroma.lol/users/lain#follows/4",
|
||||||
|
"actor": "http://localtesting.pleroma.lol/users/lain"
|
||||||
|
},
|
||||||
|
"nickname": "lain",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#rejects/follows/4",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
34
test/fixtures/mastodon-unblock-activity.json
vendored
Normal file
34
test/fixtures/mastodon-unblock-activity.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "Kn1/UkAQGJVaXBfWLAHcnwHg8YMAUqlEaBuYLazAG+pz5hqivsyrBmPV186Xzr+B4ZLExA9+SnOoNx/GOz4hBm0kAmukNSILAsUd84tcJ2yT9zc1RKtembK4WiwOw7li0+maeDN0HaB6t+6eTqsCWmtiZpprhXD8V1GGT8yG7X24fQ9oFGn+ng7lasbcCC0988Y1eGqNe7KryxcPuQz57YkDapvtONzk8gyLTkZMV4De93MyRHq6GVjQVIgtiYabQAxrX6Q8C+4P/jQoqdWJHEe+MY5JKyNaT/hMPt2Md1ok9fZQBGHlErk22/zy8bSN19GdG09HmIysBUHRYpBLig==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-02-17T13:29:31Z"
|
||||||
|
},
|
||||||
|
"type": "Undo",
|
||||||
|
"object": {
|
||||||
|
"type": "Block",
|
||||||
|
"object": "http://localtesting.pleroma.lol/users/lain",
|
||||||
|
"nickname": "lain",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#blocks/2",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin"
|
||||||
|
},
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#blocks/2/undo"
|
||||||
|
}
|
34
test/fixtures/mastodon-unfollow-activity.json
vendored
Normal file
34
test/fixtures/mastodon-unfollow-activity.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"@context":[
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot":"http://joinmastodon.org/ns#",
|
||||||
|
"sensitive":"as:sensitive",
|
||||||
|
"ostatus":"http://ostatus.org#",
|
||||||
|
"movedTo":"as:movedTo",
|
||||||
|
"manuallyApprovesFollowers":"as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri":"ostatus:inReplyToAtomUri",
|
||||||
|
"conversation":"ostatus:conversation",
|
||||||
|
"atomUri":"ostatus:atomUri",
|
||||||
|
"Hashtag":"as:Hashtag",
|
||||||
|
"Emoji":"toot:Emoji"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"signature":{
|
||||||
|
"type":"RsaSignature2017",
|
||||||
|
"signatureValue":"Kn1/UkAQGJVaXBfWLAHcnwHg8YMAUqlEaBuYLazAG+pz5hqivsyrBmPV186Xzr+B4ZLExA9+SnOoNx/GOz4hBm0kAmukNSILAsUd84tcJ2yT9zc1RKtembK4WiwOw7li0+maeDN0HaB6t+6eTqsCWmtiZpprhXD8V1GGT8yG7X24fQ9oFGn+ng7lasbcCC0988Y1eGqNe7KryxcPuQz57YkDapvtONzk8gyLTkZMV4De93MyRHq6GVjQVIgtiYabQAxrX6Q8C+4P/jQoqdWJHEe+MY5JKyNaT/hMPt2Md1ok9fZQBGHlErk22/zy8bSN19GdG09HmIysBUHRYpBLig==",
|
||||||
|
"creator":"http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created":"2018-02-17T13:29:31Z"
|
||||||
|
},
|
||||||
|
"type":"Undo",
|
||||||
|
"object":{
|
||||||
|
"type":"Follow",
|
||||||
|
"object":"http://localtesting.pleroma.lol/users/lain",
|
||||||
|
"nickname":"lain",
|
||||||
|
"id":"http://mastodon.example.org/users/admin#follows/2",
|
||||||
|
"actor":"http://mastodon.example.org/users/admin"
|
||||||
|
},
|
||||||
|
"actor":"http://mastodon.example.org/users/admin",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#follow/2/undo"
|
||||||
|
}
|
68
test/fixtures/unfollow.xml
vendored
Normal file
68
test/fixtures/unfollow.xml
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:media="http://purl.org/syndication/atommedia" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/">
|
||||||
|
<generator uri="https://gnu.io/social" version="1.0.2-dev">GNU social</generator>
|
||||||
|
<id>https://social.heldscal.la/api/statuses/user_timeline/23211.atom</id>
|
||||||
|
<title>lambadalambda timeline</title>
|
||||||
|
<subtitle>Updates from lambadalambda on social.heldscal.la!</subtitle>
|
||||||
|
<logo>https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg</logo>
|
||||||
|
<updated>2017-05-07T09:54:49+00:00</updated>
|
||||||
|
<author>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||||
|
<uri>https://social.heldscal.la/user/23211</uri>
|
||||||
|
<name>lambadalambda</name>
|
||||||
|
<summary>Call me Deacon Blues.</summary>
|
||||||
|
<link rel="alternate" type="text/html" href="https://social.heldscal.la/lambadalambda"/>
|
||||||
|
<link rel="avatar" type="image/jpeg" media:width="236" media:height="236" href="https://social.heldscal.la/avatar/23211-original-20170416114255.jpeg"/>
|
||||||
|
<link rel="avatar" type="image/jpeg" media:width="96" media:height="96" href="https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg"/>
|
||||||
|
<link rel="avatar" type="image/jpeg" media:width="48" media:height="48" href="https://social.heldscal.la/avatar/23211-48-20170416114255.jpeg"/>
|
||||||
|
<link rel="avatar" type="image/jpeg" media:width="24" media:height="24" href="https://social.heldscal.la/avatar/23211-24-20170416114257.jpeg"/>
|
||||||
|
<poco:preferredUsername>lambadalambda</poco:preferredUsername>
|
||||||
|
<poco:displayName>Constance Variable</poco:displayName>
|
||||||
|
<poco:note>Call me Deacon Blues.</poco:note>
|
||||||
|
<poco:address>
|
||||||
|
<poco:formatted>Berlin</poco:formatted>
|
||||||
|
</poco:address>
|
||||||
|
<poco:urls>
|
||||||
|
<poco:type>homepage</poco:type>
|
||||||
|
<poco:value>https://heldscal.la</poco:value>
|
||||||
|
<poco:primary>true</poco:primary>
|
||||||
|
</poco:urls>
|
||||||
|
<followers url="https://social.heldscal.la/lambadalambda/subscribers"></followers>
|
||||||
|
<statusnet:profile_info local_id="23211"></statusnet:profile_info>
|
||||||
|
</author>
|
||||||
|
<link href="https://social.heldscal.la/lambadalambda" rel="alternate" type="text/html"/>
|
||||||
|
<link href="https://social.heldscal.la/main/sup" rel="http://api.friendfeed.com/2008/03#sup" type="application/json"/>
|
||||||
|
<link href="https://social.heldscal.la/main/push/hub" rel="hub"/>
|
||||||
|
<link href="https://social.heldscal.la/main/salmon/user/23211" rel="salmon"/>
|
||||||
|
<link href="https://social.heldscal.la/main/salmon/user/23211" rel="http://salmon-protocol.org/ns/salmon-replies"/>
|
||||||
|
<link href="https://social.heldscal.la/main/salmon/user/23211" rel="http://salmon-protocol.org/ns/salmon-mention"/>
|
||||||
|
<link href="https://social.heldscal.la/api/statuses/user_timeline/23211.atom" rel="self" type="application/atom+xml"/>
|
||||||
|
<entry>
|
||||||
|
<id>undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00</id>
|
||||||
|
<title>Constance Variable (lambadalambda@social.heldscal.la)'s status on Sunday, 07-May-2017 09:54:49 UTC</title>
|
||||||
|
<content type="html"><a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> stopped following <a href="https://pawoo.net/@pekorino">mono</a>.</content>
|
||||||
|
<link rel="alternate" type="text/html" href="https://social.heldscal.la/notice/2092981"/>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/unfollow</activity:verb>
|
||||||
|
<published>2017-05-07T09:54:49+00:00</published>
|
||||||
|
<updated>2017-05-07T09:54:49+00:00</updated>
|
||||||
|
<activity:object>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||||
|
<id>https://pawoo.net/users/pekorino</id>
|
||||||
|
<title>mono</title>
|
||||||
|
<summary>http://shitposter.club/mono 孤独のグルメ</summary>
|
||||||
|
<link rel="alternate" type="text/html" href="https://pawoo.net/@pekorino"/>
|
||||||
|
<link rel="avatar" type="image/png" media:width="96" media:height="96" href="http://social.heldscal.la/theme/neo-gnu/default-avatar-profile.png"/>
|
||||||
|
<link rel="avatar" type="image/png" media:width="48" media:height="48" href="http://social.heldscal.la/theme/neo-gnu/default-avatar-stream.png"/>
|
||||||
|
<link rel="avatar" type="image/png" media:width="24" media:height="24" href="http://social.heldscal.la/theme/neo-gnu/default-avatar-mini.png"/>
|
||||||
|
<poco:preferredUsername>pekorino</poco:preferredUsername>
|
||||||
|
<poco:displayName>mono</poco:displayName>
|
||||||
|
<poco:note>http://shitposter.club/mono 孤独のグルメ</poco:note>
|
||||||
|
</activity:object>
|
||||||
|
<link rel="ostatus:conversation" href="https://social.heldscal.la/conversation/1079786"/>
|
||||||
|
<ostatus:conversation href="https://social.heldscal.la/conversation/1079786" local_id="1079786" ref="tag:social.heldscal.la,2017-05-07:objectType=thread:nonce=6e80caf94e03029f">tag:social.heldscal.la,2017-05-07:objectType=thread:nonce=6e80caf94e03029f</ostatus:conversation>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://social.heldscal.la/api/statuses/show/2092981.atom"/>
|
||||||
|
<link rel="edit" type="application/atom+xml" href="https://social.heldscal.la/api/statuses/show/2092981.atom"/>
|
||||||
|
<statusnet:notice_info local_id="2092981" source="activity"></statusnet:notice_info>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
77
test/list_test.exs
Normal file
77
test/list_test.exs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
defmodule Pleroma.ListTest do
|
||||||
|
alias Pleroma.{User, Repo}
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
test "creating a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user)
|
||||||
|
%Pleroma.List{title: title} = Pleroma.List.get(list.id, user)
|
||||||
|
assert title == "title"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting a list not belonging to the user" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user)
|
||||||
|
ret = Pleroma.List.get(list.id, other_user)
|
||||||
|
assert is_nil(ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "adding an user to a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("title", user)
|
||||||
|
{:ok, %{following: following}} = Pleroma.List.follow(list, other_user)
|
||||||
|
assert [other_user.follower_address] == following
|
||||||
|
end
|
||||||
|
|
||||||
|
test "removing an user from a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("title", user)
|
||||||
|
{:ok, %{following: following}} = Pleroma.List.follow(list, other_user)
|
||||||
|
{:ok, %{following: following}} = Pleroma.List.unfollow(list, other_user)
|
||||||
|
assert [] == following
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renaming a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("title", user)
|
||||||
|
{:ok, %{title: title}} = Pleroma.List.rename(list, "new")
|
||||||
|
assert "new" == title
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deleting a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("title", user)
|
||||||
|
{:ok, list} = Pleroma.List.delete(list)
|
||||||
|
assert is_nil(Repo.get(Pleroma.List, list.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting users in a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("title", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, third_user)
|
||||||
|
{:ok, following} = Pleroma.List.get_following(list)
|
||||||
|
assert other_user in following
|
||||||
|
assert third_user in following
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting all lists by an user" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, list_one} = Pleroma.List.create("title", user)
|
||||||
|
{:ok, list_two} = Pleroma.List.create("other title", user)
|
||||||
|
{:ok, list_three} = Pleroma.List.create("third title", other_user)
|
||||||
|
lists = Pleroma.List.for_user(user, %{})
|
||||||
|
assert list_one in lists
|
||||||
|
assert list_two in lists
|
||||||
|
refute list_three in lists
|
||||||
|
end
|
||||||
|
end
|
|
@ -122,7 +122,8 @@ def follow_activity_factory do
|
||||||
}
|
}
|
||||||
|
|
||||||
%Pleroma.Activity{
|
%Pleroma.Activity{
|
||||||
data: data
|
data: data,
|
||||||
|
actor: follower.ap_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,15 @@ test "can't follow a deactivated users" do
|
||||||
{:error, _} = User.follow(user, followed)
|
{:error, _} = User.follow(user, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can't follow a user who blocked us" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blockee = insert(:user)
|
||||||
|
|
||||||
|
{:ok, blocker} = User.block(blocker, blockee)
|
||||||
|
|
||||||
|
{:error, _} = User.follow(blockee, blocker)
|
||||||
|
end
|
||||||
|
|
||||||
# This is a somewhat useless test.
|
# This is a somewhat useless test.
|
||||||
# test "following a remote user will ensure a websub subscription is present" do
|
# test "following a remote user will ensure a websub subscription is present" do
|
||||||
# user = insert(:user)
|
# user = insert(:user)
|
||||||
|
|
|
@ -425,7 +425,40 @@ test "creates an undo activity for the last follow" do
|
||||||
|
|
||||||
assert activity.data["type"] == "Undo"
|
assert activity.data["type"] == "Undo"
|
||||||
assert activity.data["actor"] == follower.ap_id
|
assert activity.data["actor"] == follower.ap_id
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
|
||||||
|
assert is_map(activity.data["object"])
|
||||||
|
assert activity.data["object"]["type"] == "Follow"
|
||||||
|
assert activity.data["object"]["object"] == followed.ap_id
|
||||||
|
assert activity.data["object"]["id"] == follow_activity.data["id"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "blocking / unblocking" do
|
||||||
|
test "creates a block activity" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = ActivityPub.block(blocker, blocked)
|
||||||
|
|
||||||
|
assert activity.data["type"] == "Block"
|
||||||
|
assert activity.data["actor"] == blocker.ap_id
|
||||||
|
assert activity.data["object"] == blocked.ap_id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates an undo activity for the last block" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
|
||||||
|
{:ok, block_activity} = ActivityPub.block(blocker, blocked)
|
||||||
|
{:ok, activity} = ActivityPub.unblock(blocker, blocked)
|
||||||
|
|
||||||
|
assert activity.data["type"] == "Undo"
|
||||||
|
assert activity.data["actor"] == blocker.ap_id
|
||||||
|
|
||||||
|
assert is_map(activity.data["object"])
|
||||||
|
assert activity.data["object"]["type"] == "Block"
|
||||||
|
assert activity.data["object"]["object"] == blocked.ap_id
|
||||||
|
assert activity.data["object"]["id"] == block_activity.data["id"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.Web.Websub.WebsubClientSubscription
|
alias Pleroma.Web.Websub.WebsubClientSubscription
|
||||||
alias Pleroma.Web.Websub.WebsubServerSubscription
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
@ -283,7 +282,7 @@ test "it works for incoming deletes" do
|
||||||
|> Map.put("object", object)
|
|> Map.put("object", object)
|
||||||
|> Map.put("actor", activity.data["actor"])
|
|> Map.put("actor", activity.data["actor"])
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Repo.get(Activity, activity.id)
|
||||||
end
|
end
|
||||||
|
@ -315,6 +314,234 @@ test "it works for incoming unannounces with an existing notice" do
|
||||||
assert data["object"]["id"] ==
|
assert data["object"]["id"] ==
|
||||||
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works for incomming unfollows with an existing follow" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
follow_data =
|
||||||
|
File.read!("test/fixtures/mastodon-follow-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", user.ap_id)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-unfollow-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", follow_data)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["type"] == "Undo"
|
||||||
|
assert data["object"]["type"] == "Follow"
|
||||||
|
assert data["object"]["object"] == user.ap_id
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
refute User.following?(User.get_by_ap_id(data["actor"]), user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming blocks" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-block-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", user.ap_id)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["type"] == "Block"
|
||||||
|
assert data["object"] == user.ap_id
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
blocker = User.get_by_ap_id(data["actor"])
|
||||||
|
|
||||||
|
assert User.blocks?(blocker, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming unblocks with an existing block" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
block_data =
|
||||||
|
File.read!("test/fixtures/mastodon-block-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", user.ap_id)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-unblock-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", block_data)
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
assert data["type"] == "Undo"
|
||||||
|
assert data["object"]["type"] == "Block"
|
||||||
|
assert data["object"]["object"] == user.ap_id
|
||||||
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
|
|
||||||
|
blocker = User.get_by_ap_id(data["actor"])
|
||||||
|
|
||||||
|
refute User.blocks?(blocker, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming accepts which were pre-accepted" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user)
|
||||||
|
|
||||||
|
{:ok, follower} = User.follow(follower, followed)
|
||||||
|
assert User.following?(follower, followed) == true
|
||||||
|
|
||||||
|
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|
||||||
|
object =
|
||||||
|
accept_data["object"]
|
||||||
|
|> Map.put("actor", follower.ap_id)
|
||||||
|
|> Map.put("id", follow_activity.data["id"])
|
||||||
|
|
||||||
|
accept_data = Map.put(accept_data, "object", object)
|
||||||
|
|
||||||
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
refute activity.local
|
||||||
|
|
||||||
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming accepts which were orphaned" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{info: %{"locked" => true}})
|
||||||
|
|
||||||
|
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
|
||||||
|
|
||||||
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming accepts which are referenced by IRI only" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{info: %{"locked" => true}})
|
||||||
|
|
||||||
|
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|> Map.put("object", follow_activity.data["id"])
|
||||||
|
|
||||||
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it fails for incoming accepts which cannot be correlated" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{info: %{"locked" => true}})
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
|
||||||
|
|
||||||
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
refute User.following?(follower, followed) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it fails for incoming rejects which cannot be correlated" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{info: %{"locked" => true}})
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
File.read!("test/fixtures/mastodon-reject-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|
||||||
|
accept_data =
|
||||||
|
Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
|
||||||
|
|
||||||
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
refute User.following?(follower, followed) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming rejects which are orphaned" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{info: %{"locked" => true}})
|
||||||
|
|
||||||
|
{:ok, follower} = User.follow(follower, followed)
|
||||||
|
{:ok, _follow_activity} = ActivityPub.follow(follower, followed)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == true
|
||||||
|
|
||||||
|
reject_data =
|
||||||
|
File.read!("test/fixtures/mastodon-reject-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|
||||||
|
reject_data =
|
||||||
|
Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
|
||||||
|
|
||||||
|
{:ok, activity} = Transmogrifier.handle_incoming(reject_data)
|
||||||
|
refute activity.local
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming rejects which are referenced by IRI only" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{info: %{"locked" => true}})
|
||||||
|
|
||||||
|
{:ok, follower} = User.follow(follower, followed)
|
||||||
|
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == true
|
||||||
|
|
||||||
|
reject_data =
|
||||||
|
File.read!("test/fixtures/mastodon-reject-activity.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", followed.ap_id)
|
||||||
|
|> Map.put("object", follow_activity.data["id"])
|
||||||
|
|
||||||
|
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
|
||||||
|
|
||||||
|
follower = Repo.get(User, follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, followed) == false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "prepare outgoing" do
|
describe "prepare outgoing" do
|
||||||
|
|
13
test/web/common_api/common_api_test.exs
Normal file
13
test/web/common_api/common_api_test.exs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Pleroma.Web.CommonAPI.Test do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "it de-duplicates tags" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
|
||||||
|
|
||||||
|
assert activity.data["object"]["tag"] == ["2hu"]
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,13 +21,12 @@ test "it adds attachment links to a given text and attachment set" do
|
||||||
test "incorrect password given" do
|
test "incorrect password given" do
|
||||||
{:ok, user} = UserBuilder.insert()
|
{:ok, user} = UserBuilder.insert()
|
||||||
|
|
||||||
assert Utils.confirm_current_password(user, %{"password" => ""}) ==
|
assert Utils.confirm_current_password(user, "") == {:error, "Invalid password."}
|
||||||
{:error, "Invalid password."}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "correct password given" do
|
test "correct password given" do
|
||||||
{:ok, user} = UserBuilder.insert()
|
{:ok, user} = UserBuilder.insert()
|
||||||
assert Utils.confirm_current_password(user, %{"password" => "test"}) == {:ok, user}
|
assert Utils.confirm_current_password(user, "test") == {:ok, user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
19
test/web/mastodon_api/list_view_test.exs
Normal file
19
test/web/mastodon_api/list_view_test.exs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ListViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.Web.MastodonAPI.ListView
|
||||||
|
alias Pleroma.List
|
||||||
|
|
||||||
|
test "Represent a list" do
|
||||||
|
user = insert(:user)
|
||||||
|
title = "mortal enemies"
|
||||||
|
{:ok, list} = Pleroma.List.create(title, user)
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
id: to_string(list.id),
|
||||||
|
title: title
|
||||||
|
}
|
||||||
|
|
||||||
|
assert expected == ListView.render("list.json", %{list: list})
|
||||||
|
end
|
||||||
|
end
|
|
@ -229,6 +229,125 @@ test "when you didn't create it", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "lists" do
|
||||||
|
test "creating a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/lists", %{"title" => "cuties"})
|
||||||
|
|
||||||
|
assert %{"title" => title} = json_response(conn, 200)
|
||||||
|
assert title == "cuties"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "adding users to a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
|
||||||
|
|
||||||
|
assert %{} == json_response(conn, 200)
|
||||||
|
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
|
||||||
|
assert following == [other_user.follower_address]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "removing users from a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
third_user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, third_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
|
||||||
|
|
||||||
|
assert %{} == json_response(conn, 200)
|
||||||
|
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
|
||||||
|
assert following == [third_user.follower_address]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "listing users in a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(conn, 200)
|
||||||
|
assert id == to_string(other_user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "retrieving a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/lists/#{list.id}")
|
||||||
|
|
||||||
|
assert %{"id" => id} = json_response(conn, 200)
|
||||||
|
assert id == to_string(list.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renaming a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
|
||||||
|
|
||||||
|
assert %{"title" => name} = json_response(conn, 200)
|
||||||
|
assert name == "newname"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deleting a list", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/lists/#{list.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
assert is_nil(Repo.get(Pleroma.List, list.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
|
||||||
|
{:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/list/#{list.id}")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert id == to_string(activity_two.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "notifications" do
|
describe "notifications" do
|
||||||
test "list of notifications", %{conn: conn} do
|
test "list of notifications", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -278,6 +278,30 @@ test "handle incoming follows" do
|
||||||
assert User.following?(follower, followed)
|
assert User.following?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "handle incoming unfollows with existing follow" do
|
||||||
|
incoming_follow = File.read!("test/fixtures/follow.xml")
|
||||||
|
{:ok, [_activity]} = OStatus.handle_incoming(incoming_follow)
|
||||||
|
|
||||||
|
incoming = File.read!("test/fixtures/unfollow.xml")
|
||||||
|
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||||
|
|
||||||
|
assert activity.data["type"] == "Undo"
|
||||||
|
|
||||||
|
assert activity.data["id"] ==
|
||||||
|
"undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00"
|
||||||
|
|
||||||
|
assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
|
||||||
|
assert is_map(activity.data["object"])
|
||||||
|
assert activity.data["object"]["type"] == "Follow"
|
||||||
|
assert activity.data["object"]["object"] == "https://pawoo.net/users/pekorino"
|
||||||
|
refute activity.local
|
||||||
|
|
||||||
|
follower = User.get_by_ap_id(activity.data["actor"])
|
||||||
|
followed = User.get_by_ap_id(activity.data["object"]["object"])
|
||||||
|
|
||||||
|
refute User.following?(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
describe "new remote user creation" do
|
describe "new remote user creation" do
|
||||||
test "returns local users" do
|
test "returns local users" do
|
||||||
local_user = insert(:user)
|
local_user = insert(:user)
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|
||||||
alias Pleroma.Web.TwitterAPI.NotificationView
|
alias Pleroma.Web.TwitterAPI.NotificationView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
alias Comeonin.Pbkdf2
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
@ -443,7 +444,7 @@ test "without valid credentials", %{conn: conn} do
|
||||||
test "with credentials", %{conn: conn, user: current_user} do
|
test "with credentials", %{conn: conn, user: current_user} do
|
||||||
blocked = insert(:user)
|
blocked = insert(:user)
|
||||||
|
|
||||||
{:ok, current_user} = User.block(current_user, blocked)
|
{:ok, current_user, blocked} = TwitterAPI.block(current_user, %{"user_id" => blocked.id})
|
||||||
assert User.blocks?(current_user, blocked)
|
assert User.blocks?(current_user, blocked)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
|
@ -801,6 +802,82 @@ test "Convert newlines to <br> in bio", %{conn: conn} do
|
||||||
assert user.bio == "Hello,<br>World! I<br> am a test."
|
assert user.bio == "Hello,<br>World! I<br> am a test."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "POST /api/pleroma/change_password" do
|
||||||
|
setup [:valid_user]
|
||||||
|
|
||||||
|
test "without credentials", %{conn: conn} do
|
||||||
|
conn = post(conn, "/api/pleroma/change_password")
|
||||||
|
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with credentials and invalid password", %{conn: conn, user: current_user} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|
|> post("/api/pleroma/change_password", %{
|
||||||
|
"password" => "hi",
|
||||||
|
"new_password" => "newpass",
|
||||||
|
"new_password_confirmation" => "newpass"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{"error" => "Invalid password."}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with credentials, valid password and new password and confirmation not matching", %{
|
||||||
|
conn: conn,
|
||||||
|
user: current_user
|
||||||
|
} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|
|> post("/api/pleroma/change_password", %{
|
||||||
|
"password" => "test",
|
||||||
|
"new_password" => "newpass",
|
||||||
|
"new_password_confirmation" => "notnewpass"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{
|
||||||
|
"error" => "New password does not match confirmation."
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with credentials, valid password and invalid new password", %{
|
||||||
|
conn: conn,
|
||||||
|
user: current_user
|
||||||
|
} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|
|> post("/api/pleroma/change_password", %{
|
||||||
|
"password" => "test",
|
||||||
|
"new_password" => "",
|
||||||
|
"new_password_confirmation" => ""
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{
|
||||||
|
"error" => "New password can't be blank."
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with credentials, valid password and matching new password and confirmation", %{
|
||||||
|
conn: conn,
|
||||||
|
user: current_user
|
||||||
|
} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|
|> post("/api/pleroma/change_password", %{
|
||||||
|
"password" => "test",
|
||||||
|
"new_password" => "newpass",
|
||||||
|
"new_password_confirmation" => "newpass"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{"status" => "success"}
|
||||||
|
fetched_user = Repo.get(User, current_user.id)
|
||||||
|
assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "POST /api/pleroma/delete_account" do
|
describe "POST /api/pleroma/delete_account" do
|
||||||
setup [:valid_user]
|
setup [:valid_user]
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ test "Block another user using screen_name" do
|
||||||
test "Unblock another user using user_id" do
|
test "Unblock another user using user_id" do
|
||||||
unblocked = insert(:user)
|
unblocked = insert(:user)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
User.block(user, unblocked)
|
{:ok, user, _unblocked} = TwitterAPI.block(user, %{"user_id" => unblocked.id})
|
||||||
|
|
||||||
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id})
|
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id})
|
||||||
assert user.info["blocks"] == []
|
assert user.info["blocks"] == []
|
||||||
|
@ -175,7 +175,7 @@ test "Unblock another user using user_id" do
|
||||||
test "Unblock another user using screen_name" do
|
test "Unblock another user using screen_name" do
|
||||||
unblocked = insert(:user)
|
unblocked = insert(:user)
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
User.block(user, unblocked)
|
{:ok, user, _unblocked} = TwitterAPI.block(user, %{"screen_name" => unblocked.nickname})
|
||||||
|
|
||||||
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname})
|
{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname})
|
||||||
assert user.info["blocks"] == []
|
assert user.info["blocks"] == []
|
||||||
|
|
|
@ -24,7 +24,7 @@ test "A follow notification" do
|
||||||
|
|
||||||
{:ok, follower} = User.follow(follower, user)
|
{:ok, follower} = User.follow(follower, user)
|
||||||
{:ok, activity} = ActivityPub.follow(follower, user)
|
{:ok, activity} = ActivityPub.follow(follower, user)
|
||||||
Cachex.set(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id)))
|
Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id)))
|
||||||
[follow_notif] = Notification.for_user(user)
|
[follow_notif] = Notification.for_user(user)
|
||||||
|
|
||||||
represented = %{
|
represented = %{
|
||||||
|
|
|
@ -31,7 +31,7 @@ test "A user" do
|
||||||
User.follow(second_follower, user)
|
User.follow(second_follower, user)
|
||||||
User.follow(user, follower)
|
User.follow(user, follower)
|
||||||
{:ok, user} = User.update_follower_count(user)
|
{:ok, user} = User.update_follower_count(user)
|
||||||
Cachex.set(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id)))
|
Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id)))
|
||||||
|
|
||||||
image = "http://localhost:4001/images/avi.png"
|
image = "http://localhost:4001/images/avi.png"
|
||||||
banner = "http://localhost:4001/images/banner.png"
|
banner = "http://localhost:4001/images/banner.png"
|
||||||
|
|
Loading…
Reference in a new issue