commit
b6b5b16ba4
@ -0,0 +1,25 @@
|
|||||||
|
defmodule Mix.Tasks.Pleroma.Benchmark do
|
||||||
|
use Mix.Task
|
||||||
|
alias Mix.Tasks.Pleroma.Common
|
||||||
|
|
||||||
|
def run(["search"]) do
|
||||||
|
Common.start_pleroma()
|
||||||
|
|
||||||
|
Benchee.run(%{
|
||||||
|
"search" => fn ->
|
||||||
|
Pleroma.Web.MastodonAPI.MastodonAPIController.status_search(nil, "cofe")
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["tag"]) do
|
||||||
|
Common.start_pleroma()
|
||||||
|
|
||||||
|
Benchee.run(%{
|
||||||
|
"tag" => fn ->
|
||||||
|
%{"type" => "Create", "tag" => "cofe"}
|
||||||
|
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,16 @@
|
|||||||
|
defmodule Pleroma.BBS.Authenticator do
|
||||||
|
use Sshd.PasswordAuthenticator
|
||||||
|
alias Comeonin.Pbkdf2
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def authenticate(username, password) do
|
||||||
|
username = to_string(username)
|
||||||
|
password = to_string(password)
|
||||||
|
|
||||||
|
with %User{} = user <- User.get_by_nickname(username) do
|
||||||
|
Pbkdf2.checkpw(password, user.password_hash)
|
||||||
|
else
|
||||||
|
_e -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,147 @@
|
|||||||
|
defmodule Pleroma.BBS.Handler do
|
||||||
|
use Sshd.ShellHandler
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
def on_shell(username, _pubkey, _ip, _port) do
|
||||||
|
:ok = IO.puts("Welcome to #{Pleroma.Config.get([:instance, :name])}!")
|
||||||
|
user = Pleroma.User.get_cached_by_nickname(to_string(username))
|
||||||
|
Logger.debug("#{inspect(user)}")
|
||||||
|
loop(run_state(user: user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_connect(username, ip, port, method) do
|
||||||
|
Logger.debug(fn ->
|
||||||
|
"""
|
||||||
|
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
|
||||||
|
inspect(port)
|
||||||
|
} using #{inspect(method)}
|
||||||
|
"""
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_disconnect(username, ip, port) do
|
||||||
|
Logger.debug(fn ->
|
||||||
|
"Disconnecting SSH shell for #{username} from #{inspect(ip)}:#{inspect(port)}"
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp loop(state) do
|
||||||
|
self_pid = self()
|
||||||
|
counter = state.counter
|
||||||
|
prefix = state.prefix
|
||||||
|
user = state.user
|
||||||
|
|
||||||
|
input = spawn(fn -> io_get(self_pid, prefix, counter, user.nickname) end)
|
||||||
|
wait_input(state, input)
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts_activity(activity) do
|
||||||
|
status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity})
|
||||||
|
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
|
||||||
|
IO.puts(HtmlSanitizeEx.strip_tags(status.content))
|
||||||
|
IO.puts("")
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_command(state, "help") do
|
||||||
|
IO.puts("Available commands:")
|
||||||
|
IO.puts("help - This help")
|
||||||
|
IO.puts("home - Show the home timeline")
|
||||||
|
IO.puts("p <text> - Post the given text")
|
||||||
|
IO.puts("r <id> <text> - Reply to the post with the given id")
|
||||||
|
IO.puts("quit - Quit")
|
||||||
|
|
||||||
|
state
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_command(%{user: user} = state, "r " <> text) do
|
||||||
|
text = String.trim(text)
|
||||||
|
[activity_id, rest] = String.split(text, " ", parts: 2)
|
||||||
|
|
||||||
|
with %Activity{} <- Activity.get_by_id(activity_id),
|
||||||
|
{:ok, _activity} <-
|
||||||
|
CommonAPI.post(user, %{"status" => rest, "in_reply_to_status_id" => activity_id}) do
|
||||||
|
IO.puts("Replied!")
|
||||||
|
else
|
||||||
|
_e -> IO.puts("Could not reply...")
|
||||||
|
end
|
||||||
|
|
||||||
|
state
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_command(%{user: user} = state, "p " <> text) do
|
||||||
|
text = String.trim(text)
|
||||||
|
|
||||||
|
with {:ok, _activity} <- CommonAPI.post(user, %{"status" => text}) do
|
||||||
|
IO.puts("Posted!")
|
||||||
|
else
|
||||||
|
_e -> IO.puts("Could not post...")
|
||||||
|
end
|
||||||
|
|
||||||
|
state
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_command(state, "home") do
|
||||||
|
user = state.user
|
||||||
|
|
||||||
|
params =
|
||||||
|
%{}
|
||||||
|
|> Map.put("type", ["Create"])
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|
||||||
|
activities =
|
||||||
|
[user.ap_id | user.following]
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|> ActivityPub.contain_timeline(user)
|
||||||
|
|
||||||
|
Enum.each(activities, fn activity ->
|
||||||
|
puts_activity(activity)
|
||||||
|
end)
|
||||||
|
|
||||||
|
state
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_command(state, command) do
|
||||||
|
IO.puts("Unknown command '#{command}'")
|
||||||
|
state
|
||||||
|
end
|
||||||
|
|
||||||
|
defp wait_input(state, input) do
|
||||||
|
receive do
|
||||||
|
{:input, ^input, "quit\n"} ->
|
||||||
|
IO.puts("Exiting...")
|
||||||
|
|
||||||
|
{:input, ^input, code} when is_binary(code) ->
|
||||||
|
code = String.trim(code)
|
||||||
|
|
||||||
|
state = handle_command(state, code)
|
||||||
|
|
||||||
|
loop(%{state | counter: state.counter + 1})
|
||||||
|
|
||||||
|
{:error, :interrupted} ->
|
||||||
|
IO.puts("Caught Ctrl+C...")
|
||||||
|
loop(%{state | counter: state.counter + 1})
|
||||||
|
|
||||||
|
{:input, ^input, msg} ->
|
||||||
|
:ok = Logger.warn("received unknown message: #{inspect(msg)}")
|
||||||
|
loop(%{state | counter: state.counter + 1})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp run_state(opts) do
|
||||||
|
%{prefix: "pleroma", counter: 1, user: opts[:user]}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp io_get(pid, prefix, counter, username) do
|
||||||
|
prompt = prompt(prefix, counter, username)
|
||||||
|
send(pid, {:input, self(), IO.gets(:stdio, prompt)})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prompt(prefix, counter, username) do
|
||||||
|
prompt = "#{username}@#{prefix}:#{counter}>"
|
||||||
|
prompt <> " "
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,75 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Conversation do
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "conversations" do
|
||||||
|
# This is the context ap id.
|
||||||
|
field(:ap_id, :string)
|
||||||
|
has_many(:participations, Participation)
|
||||||
|
has_many(:users, through: [:participations, :user])
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def creation_cng(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:ap_id])
|
||||||
|
|> validate_required([:ap_id])
|
||||||
|
|> unique_constraint(:ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_for_ap_id(ap_id) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> creation_cng(%{ap_id: ap_id})
|
||||||
|
|> Repo.insert(
|
||||||
|
on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
|
||||||
|
returning: true,
|
||||||
|
conflict_target: :ap_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_for_ap_id(ap_id) do
|
||||||
|
Repo.get_by(__MODULE__, ap_id: ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
This will
|
||||||
|
1. Create a conversation if there isn't one already
|
||||||
|
2. Create a participation for all the people involved who don't have one already
|
||||||
|
3. Bump all relevant participations to 'unread'
|
||||||
|
"""
|
||||||
|
def create_or_bump_for(activity) do
|
||||||
|
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
|
||||||
|
object <- Pleroma.Object.normalize(activity),
|
||||||
|
"Create" <- activity.data["type"],
|
||||||
|
"Note" <- object.data["type"],
|
||||||
|
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
|
||||||
|
{:ok, conversation} = create_for_ap_id(ap_id)
|
||||||
|
|
||||||
|
users = User.get_users_from_set(activity.recipients, false)
|
||||||
|
|
||||||
|
participations =
|
||||||
|
Enum.map(users, fn user ->
|
||||||
|
{:ok, participation} =
|
||||||
|
Participation.create_for_user_and_conversation(user, conversation)
|
||||||
|
|
||||||
|
participation
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
conversation
|
||||||
|
| participations: participations
|
||||||
|
}}
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,81 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Conversation.Participation do
|
||||||
|
use Ecto.Schema
|
||||||
|
alias Pleroma.Conversation
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
schema "conversation_participations" do
|
||||||
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
belongs_to(:conversation, Conversation)
|
||||||
|
field(:read, :boolean, default: false)
|
||||||
|
field(:last_activity_id, Pleroma.FlakeId, virtual: true)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def creation_cng(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:user_id, :conversation_id])
|
||||||
|
|> validate_required([:user_id, :conversation_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_for_user_and_conversation(user, conversation) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
|
||||||
|
|> Repo.insert(
|
||||||
|
on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
|
||||||
|
returning: true,
|
||||||
|
conflict_target: [:user_id, :conversation_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_cng(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:read])
|
||||||
|
|> validate_required([:read])
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_read(participation) do
|
||||||
|
participation
|
||||||
|
|> read_cng(%{read: true})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def mark_as_unread(participation) do
|
||||||
|
participation
|
||||||
|
|> read_cng(%{read: false})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user(user, params \\ %{}) do
|
||||||
|
from(p in __MODULE__,
|
||||||
|
where: p.user_id == ^user.id,
|
||||||
|
order_by: [desc: p.updated_at]
|
||||||
|
)
|
||||||
|
|> Pleroma.Pagination.fetch_paginated(params)
|
||||||
|
|> Repo.preload(conversation: [:users])
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_with_last_activity_id(user, params \\ %{}) do
|
||||||
|
for_user(user, params)
|
||||||
|
|> Enum.map(fn participation ->
|
||||||
|
activity_id =
|
||||||
|
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||||
|
"user" => user,
|
||||||
|
"blocking_user" => user
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
participation
|
||||||
|
| last_activity_id: activity_id
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,38 @@
|
|||||||
|
defmodule Pleroma.Web.MastodonAPI.ConversationView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
def render("participation.json", %{participation: participation, user: user}) do
|
||||||
|
participation = Repo.preload(participation, conversation: :users)
|
||||||
|
|
||||||
|
last_activity_id =
|
||||||
|
with nil <- participation.last_activity_id do
|
||||||
|
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||||
|
"user" => user,
|
||||||
|
"blocking_user" => user
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
activity = Activity.get_by_id_with_object(last_activity_id)
|
||||||
|
|
||||||
|
last_status = StatusView.render("status.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
|
accounts =
|
||||||
|
AccountView.render("accounts.json", %{
|
||||||
|
users: participation.conversation.users,
|
||||||
|
as: :user
|
||||||
|
})
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: participation.id |> to_string(),
|
||||||
|
accounts: accounts,
|
||||||
|
unread: !participation.read,
|
||||||
|
last_status: last_status
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,54 @@
|
|||||||
|
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
|
||||||
|
@moduledoc """
|
||||||
|
Functions for dealing with refresh token strategy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
alias Pleroma.Web.OAuth.Token.Strategy.Revoke
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Will grant access token by refresh token.
|
||||||
|
"""
|
||||||
|
@spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()}
|
||||||
|
def grant(token) do
|
||||||
|
access_token = Repo.preload(token, [:user, :app])
|
||||||
|
|
||||||
|
result =
|
||||||
|
Repo.transaction(fn ->
|
||||||
|
token_params = %{
|
||||||
|
app: access_token.app,
|
||||||
|
user: access_token.user,
|
||||||
|
scopes: access_token.scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
access_token
|
||||||
|
|> revoke_access_token()
|
||||||
|
|> create_access_token(token_params)
|
||||||
|
end)
|
||||||
|
|
||||||
|
case result do
|
||||||
|
{:ok, {:error, reason}} -> {:error, reason}
|
||||||
|
{:ok, {:ok, token}} -> {:ok, token}
|
||||||
|
{:error, reason} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp revoke_access_token(token) do
|
||||||
|
Revoke.revoke(token)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_access_token({:error, error}, _), do: {:error, error}
|
||||||
|
|
||||||
|
defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
|
||||||
|
Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_refresh_token(params, token) do
|
||||||
|
case Config.get([:oauth2, :issue_new_refresh_token], false) do
|
||||||
|
true -> Map.put(params, :refresh_token, token)
|
||||||
|
false -> params
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,22 @@
|
|||||||
|
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
|
||||||
|
@moduledoc """
|
||||||
|
Functions for dealing with revocation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Web.OAuth.App
|
||||||
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
|
||||||
|
@doc "Finds and revokes access token for app and by token"
|
||||||
|
@spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()}
|
||||||
|
def revoke(%App{} = app, %{"token" => token} = _attrs) do
|
||||||
|
with {:ok, token} <- Token.get_by_token(app, token),
|
||||||
|
do: revoke(token)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Revokes access token"
|
||||||
|
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def revoke(%Token{} = token) do
|
||||||
|
Repo.delete(token)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,30 @@
|
|||||||
|
defmodule Pleroma.Web.OAuth.Token.Utils do
|
||||||
|
@moduledoc """
|
||||||
|
Auxiliary functions for dealing with tokens.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc "convert token inserted_at to unix timestamp"
|
||||||
|
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
||||||
|
inserted_at
|
||||||
|
|> DateTime.from_naive!("Etc/UTC")
|
||||||
|
|> DateTime.to_unix()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec generate_token(keyword()) :: binary()
|
||||||
|
def generate_token(opts \\ []) do
|
||||||
|
opts
|
||||||
|
|> Keyword.get(:size, 32)
|
||||||
|
|> :crypto.strong_rand_bytes()
|
||||||
|
|> Base.url_encode64(padding: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||||
|
# decoding it. Investigate sometime.
|
||||||
|
def fix_padding(token) do
|
||||||
|
token
|
||||||
|
|> URI.decode()
|
||||||
|
|> Base.url_decode64!(padding: false)
|
||||||
|
|> Base.url_encode64(padding: false)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,26 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateConversations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:conversations) do
|
||||||
|
add(:ap_id, :string, null: false)
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create table(:conversation_participations) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:conversation_id, references(:conversations, on_delete: :delete_all))
|
||||||
|
add(:read, :boolean, default: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create index(:conversation_participations, [:conversation_id])
|
||||||
|
create unique_index(:conversation_participations, [:user_id, :conversation_id])
|
||||||
|
create unique_index(:conversations, [:ap_id])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,7 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.AddParticipationUpdatedAtIndex do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create index(:conversation_participations, ["updated_at desc"])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,8 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.AddFTSIndexToObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
drop_if_exists index(:activities, ["(to_tsvector('english', data->'object'->>'content'))"], using: :gin, name: :activities_fts)
|
||||||
|
create index(:objects, ["(to_tsvector('english', data->>'content'))"], using: :gin, name: :objects_fts)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,7 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.AddRefreshTokenIndexToToken do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create(unique_index(:oauth_tokens, [:refresh_token]))
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,83 @@
|
|||||||
|
defmodule Pleroma.BBS.HandlerTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.BBS.Handler
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
test "getting the home timeline" do
|
||||||
|
user = insert(:user)
|
||||||
|
followed = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, followed)
|
||||||
|
|
||||||
|
{:ok, _first} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
|
{:ok, _second} = CommonAPI.post(followed, %{"status" => "hello"})
|
||||||
|
|
||||||
|
output =
|
||||||
|
capture_io(fn ->
|
||||||
|
Handler.handle_command(%{user: user}, "home")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert output =~ user.nickname
|
||||||
|
assert output =~ followed.nickname
|
||||||
|
|
||||||
|
assert output =~ "hey"
|
||||||
|
assert output =~ "hello"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "posting" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
output =
|
||||||
|
capture_io(fn ->
|
||||||
|
Handler.handle_command(%{user: user}, "p this is a test post")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert output =~ "Posted"
|
||||||
|
|
||||||
|
activity =
|
||||||
|
Repo.one(
|
||||||
|
from(a in Activity,
|
||||||
|
where: fragment("?->>'type' = ?", a.data, "Create")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert activity.actor == user.ap_id
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
assert object.data["content"] == "this is a test post"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "replying" do
|
||||||
|
user = insert(:user)
|
||||||
|
another_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "this is a test post"})
|
||||||
|
|
||||||
|
output =
|
||||||
|
capture_io(fn ->
|
||||||
|
Handler.handle_command(%{user: user}, "r #{activity.id} this is a reply")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert output =~ "Replied"
|
||||||
|
|
||||||
|
reply =
|
||||||
|
Repo.one(
|
||||||
|
from(a in Activity,
|
||||||
|
where: fragment("?->>'type' = ?", a.data, "Create"),
|
||||||
|
where: a.actor == ^user.ap_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert reply.actor == user.ap_id
|
||||||
|
object = Object.normalize(reply)
|
||||||
|
assert object.data["content"] == "this is a reply"
|
||||||
|
assert object.data["inReplyTo"] == activity.data["object"]
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,89 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Conversation.ParticipationTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
test "it creates a participation for a conversation and a user" do
|
||||||
|
user = insert(:user)
|
||||||
|
conversation = insert(:conversation)
|
||||||
|
|
||||||
|
{:ok, %Participation{} = participation} =
|
||||||
|
Participation.create_for_user_and_conversation(user, conversation)
|
||||||
|
|
||||||
|
assert participation.user_id == user.id
|
||||||
|
assert participation.conversation_id == conversation.id
|
||||||
|
|
||||||
|
:timer.sleep(1000)
|
||||||
|
# Creating again returns the same participation
|
||||||
|
{:ok, %Participation{} = participation_two} =
|
||||||
|
Participation.create_for_user_and_conversation(user, conversation)
|
||||||
|
|
||||||
|
assert participation.id == participation_two.id
|
||||||
|
refute participation.updated_at == participation_two.updated_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "recreating an existing participations sets it to unread" do
|
||||||
|
participation = insert(:participation, %{read: true})
|
||||||
|
|
||||||
|
{:ok, participation} =
|
||||||
|
Participation.create_for_user_and_conversation(
|
||||||
|
participation.user,
|
||||||
|
participation.conversation
|
||||||
|
)
|
||||||
|
|
||||||
|
refute participation.read
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it marks a participation as read" do
|
||||||
|
participation = insert(:participation, %{read: false})
|
||||||
|
{:ok, participation} = Participation.mark_as_read(participation)
|
||||||
|
|
||||||
|
assert participation.read
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it marks a participation as unread" do
|
||||||
|
participation = insert(:participation, %{read: true})
|
||||||
|
{:ok, participation} = Participation.mark_as_unread(participation)
|
||||||
|
|
||||||
|
refute participation.read
|
||||||
|
end
|
||||||
|
|
||||||
|
test "gets all the participations for a user, ordered by updated at descending" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity_one} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"})
|
||||||
|
:timer.sleep(1000)
|
||||||
|
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "x", "visibility" => "direct"})
|
||||||
|
:timer.sleep(1000)
|
||||||
|
|
||||||
|
{:ok, activity_three} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "x",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity_one.id
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [participation_one, participation_two] = Participation.for_user(user)
|
||||||
|
|
||||||
|
object2 = Pleroma.Object.normalize(activity_two)
|
||||||
|
object3 = Pleroma.Object.normalize(activity_three)
|
||||||
|
|
||||||
|
assert participation_one.conversation.ap_id == object3.data["context"]
|
||||||
|
assert participation_two.conversation.ap_id == object2.data["context"]
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
assert [participation_one] = Participation.for_user(user, %{"limit" => 1})
|
||||||
|
|
||||||
|
assert participation_one.conversation.ap_id == object3.data["context"]
|
||||||
|
|
||||||
|
# With last_activity_id
|
||||||
|
assert [participation_one] =
|
||||||
|
Participation.for_user_with_last_activity_id(user, %{"limit" => 1})
|
||||||
|
|
||||||
|
assert participation_one.last_activity_id == activity_three.id
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,137 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ConversationTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Conversation
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "it creates a conversation for given ap_id" do
|
||||||
|
assert {:ok, %Conversation{} = conversation} =
|
||||||
|
Conversation.create_for_ap_id("https://some_ap_id")
|
||||||
|
|
||||||
|
# Inserting again returns the same
|
||||||
|
assert {:ok, conversation_two} = Conversation.create_for_ap_id("https://some_ap_id")
|
||||||
|
assert conversation_two.id == conversation.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "public posts don't create conversations" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey"})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation = Conversation.get_for_ap_id(context)
|
||||||
|
|
||||||
|
refute conversation
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it creates or updates a conversation and participations for a given DM" do
|
||||||
|
har = insert(:user)
|
||||||
|
jafnhar = insert(:user, local: false)
|
||||||
|
tridi = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation =
|
||||||
|
Conversation.get_for_ap_id(context)
|
||||||
|
|> Repo.preload(:participations)
|
||||||
|
|
||||||
|
assert conversation
|
||||||
|
|
||||||
|
assert Enum.find(conversation.participations, fn %{user_id: user_id} -> har.id == user_id end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation.participations, fn %{user_id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(jafnhar, %{
|
||||||
|
"status" => "Hey @#{har.nickname}",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation_two =
|
||||||
|
Conversation.get_for_ap_id(context)
|
||||||
|
|> Repo.preload(:participations)
|
||||||
|
|
||||||
|
assert conversation_two.id == conversation.id
|
||||||
|
|
||||||
|
assert Enum.find(conversation_two.participations, fn %{user_id: user_id} ->
|
||||||
|
har.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_two.participations, fn %{user_id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(tridi, %{
|
||||||
|
"status" => "Hey @#{har.nickname}",
|
||||||
|
"visibility" => "direct",
|
||||||
|
"in_reply_to_status_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
object = Pleroma.Object.normalize(activity)
|
||||||
|
context = object.data["context"]
|
||||||
|
|
||||||
|
conversation_three =
|
||||||
|
Conversation.get_for_ap_id(context)
|
||||||
|
|> Repo.preload([:participations, :users])
|
||||||
|
|
||||||
|
assert conversation_three.id == conversation.id
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.participations, fn %{user_id: user_id} ->
|
||||||
|
har.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.participations, fn %{user_id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.participations, fn %{user_id: user_id} ->
|
||||||
|
tridi.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.users, fn %{id: user_id} ->
|
||||||
|
har.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.users, fn %{id: user_id} ->
|
||||||
|
jafnhar.id == user_id
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.find(conversation_three.users, fn %{id: user_id} ->
|
||||||
|
tridi.id == user_id
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_or_bump_for returns the conversation with participations" do
|
||||||
|
har = insert(:user)
|
||||||
|
jafnhar = insert(:user, local: false)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "direct"})
|
||||||
|
|
||||||
|
{:ok, conversation} = Conversation.create_or_bump_for(activity)
|
||||||
|
|
||||||
|
assert length(conversation.participations) == 2
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(har, %{"status" => "Hey @#{jafnhar.nickname}", "visibility" => "public"})
|
||||||
|
|
||||||
|
assert {:error, _} = Conversation.create_or_bump_for(activity)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,44 @@
|
|||||||
|
defmodule Pleroma.RepoTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "find_resource/1" do
|
||||||
|
test "returns user" do
|
||||||
|
user = insert(:user)
|
||||||
|
query = from(t in Pleroma.User, where: t.id == ^user.id)
|
||||||
|
assert Repo.find_resource(query) == {:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns not_found" do
|
||||||
|
query = from(t in Pleroma.User, where: t.id == ^"9gBuXNpD2NyDmmxxdw")
|
||||||
|
assert Repo.find_resource(query) == {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_assoc/2" do
|
||||||
|
test "get assoc from preloaded data" do
|
||||||
|
user = %Pleroma.User{name: "Agent Smith"}
|
||||||
|
token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user}
|
||||||
|
assert Repo.get_assoc(token, :user) == {:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get one-to-one assoc from repo" do
|
||||||
|
user = insert(:user, name: "Jimi Hendrix")
|
||||||
|
token = refresh_record(insert(:oauth_token, user: user))
|
||||||
|
|
||||||
|
assert Repo.get_assoc(token, :user) == {:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get one-to-many assoc from repo" do
|
||||||
|
user = insert(:user)
|
||||||
|
notification = refresh_record(insert(:notification, user: user))
|
||||||
|
|
||||||
|
assert Repo.get_assoc(user, :notifications) == {:ok, [notification]}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "return error if has not assoc " do
|
||||||
|
token = insert(:oauth_token, user: nil)
|
||||||
|
assert Repo.get_assoc(token, :user) == {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,42 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Auth.AuthenticatorTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.Auth.Authenticator
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "fetch_user/1" do
|
||||||
|
test "returns user by name" do
|
||||||
|
user = insert(:user)
|
||||||
|
assert Authenticator.fetch_user(user.nickname) == user
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns user by email" do
|
||||||
|
user = insert(:user)
|
||||||
|
assert Authenticator.fetch_user(user.email) == user
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns nil" do
|
||||||
|
assert Authenticator.fetch_user("email") == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "fetch_credentials/1" do
|
||||||
|
test "returns name and password from authorization params" do
|
||||||
|
params = %{"authorization" => %{"name" => "test", "password" => "test-pass"}}
|
||||||
|
assert Authenticator.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns name and password with grant_type 'password'" do
|
||||||
|
params = %{"grant_type" => "password", "username" => "test", "password" => "test-pass"}
|
||||||
|
assert Authenticator.fetch_credentials(params) == {:ok, {"test", "test-pass"}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error" do
|
||||||
|
assert Authenticator.fetch_credentials(%{}) == {:error, :invalid_credentials}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in new issue