@@ -1,11 +1,12 @@ | |||
defmodule Pleroma.Activity do | |||
use Ecto.Schema | |||
alias Pleroma.{Repo, Activity} | |||
alias Pleroma.{Repo, Activity, Notification} | |||
import Ecto.Query | |||
schema "activities" do | |||
field :data, :map | |||
field :local, :boolean, default: true | |||
has_many :notifications, Notification | |||
timestamps() | |||
end | |||
@@ -0,0 +1,38 @@ | |||
defmodule Pleroma.Notification do | |||
use Ecto.Schema | |||
alias Pleroma.{User, Activity, Notification, Repo} | |||
import Ecto.Query | |||
schema "notifications" do | |||
field :seen, :boolean, default: false | |||
belongs_to :user, Pleroma.User | |||
belongs_to :activity, Pleroma.Activity | |||
timestamps() | |||
end | |||
def for_user(user, opts \\ %{}) do | |||
query = from n in Notification, | |||
where: n.user_id == ^user.id, | |||
order_by: [desc: n.id], | |||
preload: [:activity], | |||
limit: 20 | |||
Repo.all(query) | |||
end | |||
def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do | |||
users = User.get_notified_from_activity(activity) | |||
notifications = Enum.map(users, fn (user) -> create_notification(activity, user) end) | |||
{:ok, notifications} | |||
end | |||
def create_notifications(_), do: {:ok, []} | |||
# TODO move to sql, too. | |||
def create_notification(%Activity{} = activity, %User{} = user) do | |||
notification = %Notification{user_id: user.id, activity_id: activity.id} | |||
{:ok, notification} = Repo.insert(notification) | |||
notification | |||
end | |||
end | |||
@@ -0,0 +1,22 @@ | |||
defmodule Pleroma.Plugs.OAuthPlug do | |||
import Plug.Conn | |||
alias Pleroma.User | |||
alias Pleroma.Repo | |||
alias Pleroma.Web.OAuth.Token | |||
def init(options) do | |||
options | |||
end | |||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn | |||
def call(conn, opts) do | |||
with ["Bearer " <> header] <- get_req_header(conn, "authorization"), | |||
%Token{user_id: user_id} <- Repo.get_by(Token, token: header), | |||
%User{} = user <- Repo.get(User, user_id) do | |||
conn | |||
|> assign(:user, user) | |||
else | |||
_ -> conn | |||
end | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.User do | |||
use Ecto.Schema | |||
import Ecto.{Changeset, Query} | |||
alias Pleroma.{Repo, User, Object, Web} | |||
alias Pleroma.{Repo, User, Object, Web, Activity, Notification} | |||
alias Comeonin.Pbkdf2 | |||
alias Pleroma.Web.{OStatus, Websub} | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
@@ -22,6 +22,7 @@ defmodule Pleroma.User do | |||
field :local, :boolean, default: true | |||
field :info, :map, default: %{} | |||
field :follower_address, :string | |||
has_many :notifications, Notification | |||
timestamps() | |||
end | |||
@@ -239,4 +240,12 @@ defmodule Pleroma.User do | |||
Repo.update(cs) | |||
end | |||
def get_notified_from_activity(%Activity{data: %{"to" => to}} = activity) do | |||
query = from u in User, | |||
where: u.ap_id in ^to, | |||
where: u.local == true | |||
Repo.all(query) | |||
end | |||
end |
@@ -1,5 +1,5 @@ | |||
defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
alias Pleroma.{Activity, Repo, Object, Upload, User, Web} | |||
alias Pleroma.{Activity, Repo, Object, Upload, User, Web, Notification} | |||
alias Ecto.{Changeset, UUID} | |||
import Ecto.Query | |||
import Pleroma.Web.ActivityPub.Utils | |||
@@ -9,7 +9,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
with nil <- Activity.get_by_ap_id(map["id"]), | |||
map <- lazy_put_activity_defaults(map), | |||
:ok <- insert_full_object(map) do | |||
Repo.insert(%Activity{data: map, local: local}) | |||
{:ok, activity} = Repo.insert(%Activity{data: map, local: local}) | |||
Notification.create_notifications(activity) | |||
{:ok, activity} | |||
else | |||
%Activity{} = activity -> {:ok, activity} | |||
error -> {:error, error} | |||
@@ -133,6 +135,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
defp restrict_actor(query, _), do: query | |||
defp restrict_type(query, %{"type" => type}) do | |||
from activity in query, | |||
where: fragment("?->>'type' = ?", activity.data, ^type) | |||
end | |||
defp restrict_type(query, _), do: query | |||
def fetch_activities(recipients, opts \\ %{}) do | |||
base_query = from activity in Activity, | |||
limit: 20, | |||
@@ -144,6 +152,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
|> restrict_local(opts) | |||
|> restrict_max(opts) | |||
|> restrict_actor(opts) | |||
|> restrict_type(opts) | |||
|> Repo.all | |||
|> Enum.reverse | |||
end | |||
@@ -0,0 +1,56 @@ | |||
defmodule Pleroma.Web.CommonAPI do | |||
alias Pleroma.{Repo, Activity, Object} | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
def delete(activity_id, user) do | |||
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id), | |||
%Object{} = object <- Object.get_by_ap_id(object_id), | |||
true <- user.ap_id == object.data["actor"], | |||
{:ok, delete} <- ActivityPub.delete(object) do | |||
{:ok, delete} | |||
end | |||
end | |||
def repeat(id_or_ap_id, user) do | |||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), | |||
false <- activity.data["actor"] == user.ap_id, | |||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do | |||
ActivityPub.announce(user, object) | |||
else | |||
_ -> | |||
{:error, "Could not repeat"} | |||
end | |||
end | |||
def favorite(id_or_ap_id, user) do | |||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), | |||
false <- activity.data["actor"] == user.ap_id, | |||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do | |||
ActivityPub.like(user, object) | |||
else | |||
_ -> | |||
{:error, "Could not favorite"} | |||
end | |||
end | |||
def unfavorite(id_or_ap_id, user) do | |||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), | |||
false <- activity.data["actor"] == user.ap_id, | |||
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do | |||
ActivityPub.unlike(user, object) | |||
else | |||
_ -> | |||
{:error, "Could not unfavorite"} | |||
end | |||
end | |||
# This is a hack for twidere. | |||
def get_by_id_or_ap_id(id) do | |||
activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id) | |||
if activity.data["type"] == "Create" do | |||
activity | |||
else | |||
Activity.get_create_activity_by_object_ap_id(activity.data["object"]) | |||
end | |||
end | |||
end |
@@ -0,0 +1,162 @@ | |||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.{Repo, Activity, User, Notification} | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web | |||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView} | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.Web.CommonAPI | |||
import Logger | |||
def create_app(conn, params) do | |||
with cs <- App.register_changeset(%App{}, params) |> IO.inspect, | |||
{:ok, app} <- Repo.insert(cs) |> IO.inspect do | |||
res = %{ | |||
id: app.id, | |||
client_id: app.client_id, | |||
client_secret: app.client_secret | |||
} | |||
json(conn, res) | |||
end | |||
end | |||
def verify_credentials(%{assigns: %{user: user}} = conn, params) do | |||
account = AccountView.render("account.json", %{user: user}) | |||
json(conn, account) | |||
end | |||
def masto_instance(conn, _params) do | |||
response = %{ | |||
uri: Web.base_url, | |||
title: Web.base_url, | |||
description: "A Pleroma instance, an alternative fediverse server", | |||
version: "Pleroma Dev" | |||
} | |||
json(conn, response) | |||
end | |||
def home_timeline(%{assigns: %{user: user}} = conn, params) do | |||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], Map.put(params, "type", "Create")) | |||
|> Enum.reverse | |||
render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity} | |||
end | |||
def public_timeline(%{assigns: %{user: user}} = conn, params) do | |||
params = params | |||
|> Map.put("type", "Create") | |||
|> Map.put("local_only", !!params["local"]) | |||
activities = ActivityPub.fetch_public_activities(params) | |||
|> Enum.reverse | |||
render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity} | |||
end | |||
def user_statuses(%{assigns: %{user: user}} = conn, params) do | |||
with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do | |||
params = params | |||
|> Map.put("type", "Create") | |||
|> Map.put("actor_id", ap_id) | |||
activities = ActivityPub.fetch_activities([], params) | |||
|> Enum.reverse | |||
render conn, StatusView, "index.json", %{activities: activities, for: user, as: :activity} | |||
end | |||
end | |||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
with %Activity{} = activity <- Repo.get(Activity, id) do | |||
render conn, StatusView, "status.json", %{activity: activity, for: user} | |||
end | |||
end | |||
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
with %Activity{} = activity <- Repo.get(Activity, id), | |||
activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"]), | |||
activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end), | |||
grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do | |||
result = %{ | |||
ancestors: StatusView.render("index.json", for: user, activities: grouped_activities[true] || [], as: :activity) |> Enum.reverse, | |||
descendants: StatusView.render("index.json", for: user, activities: grouped_activities[false] || [], as: :activity) |> Enum.reverse, | |||
} | |||
json(conn, result) | |||
end | |||
end | |||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => status} = params) do | |||
l = status |> String.trim |> String.length | |||
params = params | |||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) | |||
if l > 0 && l < 5000 do | |||
{:ok, activity} = TwitterAPI.create_status(user, params) | |||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} | |||
end | |||
end | |||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do | |||
json(conn, %{}) | |||
else | |||
_e -> | |||
conn | |||
|> put_status(403) | |||
|> json(%{error: "Can't delete this post"}) | |||
end | |||
end | |||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do | |||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user), | |||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do | |||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} | |||
end | |||
end | |||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do | |||
with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), | |||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do | |||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} | |||
end | |||
end | |||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do | |||
with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), | |||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do | |||
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity} | |||
end | |||
end | |||
def notifications(%{assigns: %{user: user}} = conn, params) do | |||
notifications = Notification.for_user(user, params) | |||
result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) -> | |||
actor = User.get_cached_by_ap_id(activity.data["actor"]) | |||
case activity.data["type"] do | |||
"Create" -> | |||
%{id: id, type: "mention", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: activity})} | |||
"Like" -> | |||
liked_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) | |||
%{id: id, type: "favourite", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: liked_activity})} | |||
"Announce" -> | |||
announced_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]) | |||
%{id: id, type: "reblog", created_at: created_at, account: AccountView.render("account.json", %{user: actor}), status: StatusView.render("status.json", %{activity: announced_activity})} | |||
"Follow" -> | |||
%{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})} | |||
_ -> nil | |||
end | |||
end) | |||
|> Enum.filter(&(&1)) | |||
json(conn, result) | |||
end | |||
def empty_array(conn, _) do | |||
Logger.debug("Unimplemented, returning an empty array") | |||
json(conn, []) | |||
end | |||
end |
@@ -0,0 +1,41 @@ | |||
defmodule Pleroma.Web.MastodonAPI.AccountView do | |||
use Pleroma.Web, :view | |||
alias Pleroma.User | |||
defp image_url(%{"url" => [ %{ "href" => href } | t ]}), do: href | |||
defp image_url(_), do: nil | |||
def render("account.json", %{user: user}) do | |||
image = User.avatar_url(user) | |||
user_info = User.user_info(user) | |||
header = image_url(user.info["banner"]) || "https://placehold.it/700x335" | |||
%{ | |||
id: user.id, | |||
username: user.nickname, | |||
acct: user.nickname, | |||
display_name: user.name, | |||
locked: false, | |||
created_at: user.inserted_at, | |||
followers_count: user_info.follower_count, | |||
following_count: user_info.following_count, | |||
statuses_count: user_info.note_count, | |||
note: user.bio, | |||
url: user.ap_id, | |||
avatar: image, | |||
avatar_static: image, | |||
header: header, | |||
header_static: header | |||
} | |||
end | |||
def render("mention.json", %{user: user}) do | |||
%{ | |||
id: user.id, | |||
acct: user.nickname, | |||
username: user.nickname, | |||
url: user.ap_id | |||
} | |||
end | |||
end |
@@ -0,0 +1,72 @@ | |||
defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
use Pleroma.Web, :view | |||
alias Pleroma.Web.MastodonAPI.{AccountView, StatusView} | |||
alias Pleroma.User | |||
def render("index.json", opts) do | |||
render_many(opts.activities, StatusView, "status.json", opts) | |||
end | |||
def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do | |||
user = User.get_cached_by_ap_id(activity.data["actor"]) | |||
like_count = object["like_count"] || 0 | |||
announcement_count = object["announcement_count"] || 0 | |||
tags = object["tag"] || [] | |||
sensitive = Enum.member?(tags, "nsfw") | |||
mentions = activity.data["to"] | |||
|> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) | |||
|> Enum.filter(&(&1)) | |||
|> Enum.map(fn (user) -> AccountView.render("mention.json", %{user: user}) end) | |||
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) | |||
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) | |||
attachments = render_many(object["attachment"] || [], StatusView, "attachment.json", as: :attachment) | |||
%{ | |||
id: activity.id, | |||
uri: object["id"], | |||
url: object["external_url"], | |||
account: AccountView.render("account.json", %{user: user}), | |||
in_reply_to_id: object["inReplyToStatusId"], | |||
in_reply_to_account_id: nil, | |||
reblog: nil, | |||
content: HtmlSanitizeEx.basic_html(object["content"]), | |||
created_at: object["published"], | |||
reblogs_count: announcement_count, | |||
favourites_count: like_count, | |||
reblogged: !!repeated, | |||
favourited: !!favorited, | |||
muted: false, | |||
sensitive: sensitive, | |||
spoiler_text: "", | |||
visibility: "public", | |||
media_attachments: attachments, | |||
mentions: mentions, | |||
tags: [], # fix, | |||
application: nil, | |||
language: nil | |||
} | |||
end | |||
def render("attachment.json", %{attachment: attachment}) do | |||
[%{"mediaType" => media_type, "href" => href} | _] = attachment["url"] | |||
type = cond do | |||
String.contains?(media_type, "image") -> "image" | |||
String.contains?(media_type, "video") -> "video" | |||
true -> "unknown" | |||
end | |||
%{ | |||
id: attachment["uuid"], | |||
url: href, | |||
remote_url: href, | |||
preview_url: href, | |||
type: type | |||
} | |||
end | |||
end |
@@ -0,0 +1,29 @@ | |||
defmodule Pleroma.Web.OAuth.App do | |||
use Ecto.Schema | |||
import Ecto.{Changeset} | |||
schema "apps" do | |||
field :client_name, :string | |||
field :redirect_uris, :string | |||
field :scopes, :string | |||
field :website, :string | |||
field :client_id, :string | |||
field :client_secret, :string | |||
timestamps() | |||
end | |||
def register_changeset(struct, params \\ %{}) do | |||
changeset = struct | |||
|> cast(params, [:client_name, :redirect_uris, :scopes, :website]) | |||
|> validate_required([:client_name, :redirect_uris, :scopes]) | |||
if changeset.valid? do | |||
changeset | |||
|> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64) | |||
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64) | |||
else | |||
changeset | |||
end | |||
end | |||
end |
@@ -0,0 +1,47 @@ | |||
defmodule Pleroma.Web.OAuth.Authorization do | |||
use Ecto.Schema | |||
alias Pleroma.{User, Repo} | |||
alias Pleroma.Web.OAuth.{Authorization, App} | |||
import Ecto.{Changeset} | |||
schema "oauth_authorizations" do | |||
field :token, :string | |||
field :valid_until, :naive_datetime | |||
field :used, :boolean, default: false | |||
belongs_to :user, Pleroma.User | |||
belongs_to :app, Pleroma.App | |||
timestamps() | |||
end | |||
def create_authorization(%App{} = app, %User{} = user) do | |||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 | |||
authorization = %Authorization{ | |||
token: token, | |||
used: false, | |||
user_id: user.id, | |||
app_id: app.id, | |||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10) | |||
} | |||
Repo.insert(authorization) | |||
end | |||
def use_changeset(%Authorization{} = auth, params) do | |||
auth | |||
|> cast(params, [:used]) | |||
|> validate_required([:used]) | |||
end | |||
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do | |||
if NaiveDateTime.diff(NaiveDateTime.utc_now, valid_until) < 0 do | |||
Repo.update(use_changeset(auth, %{used: true})) | |||
else | |||
{:error, "token expired"} | |||
end | |||
end | |||
def use_token(%Authorization{used: true}), do: {:error, "already used"} | |||
end |
@@ -0,0 +1,49 @@ | |||
defmodule Pleroma.Web.OAuth.OAuthController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Web.OAuth.{Authorization, Token, App} | |||
alias Pleroma.{Repo, User} | |||
alias Comeonin.Pbkdf2 | |||
def authorize(conn, params) do | |||
render conn, "show.html", %{ | |||
response_type: params["response_type"], | |||
client_id: params["client_id"], | |||
scope: params["scope"], | |||
redirect_uri: params["redirect_uri"] | |||
} | |||
end | |||
def create_authorization(conn, %{"authorization" => %{"name" => name, "password" => password, "client_id" => client_id, "redirect_uri" => redirect_uri}} = params) do | |||
with %User{} = user <- User.get_cached_by_nickname(name), | |||
true <- Pbkdf2.checkpw(password, user.password_hash), | |||
%App{} = app <- Repo.get_by(App, client_id: client_id), | |||
{:ok, auth} <- Authorization.create_authorization(app, user) do | |||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do | |||
render conn, "results.html", %{ | |||
auth: auth | |||
} | |||
else | |||
url = "#{redirect_uri}?code=#{auth.token}" | |||
redirect(conn, external: url) | |||
end | |||
end | |||
end | |||
# TODO | |||
# - proper scope handling | |||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do | |||
with %App{} = app <- Repo.get_by(App, client_id: params["client_id"], client_secret: params["client_secret"]), | |||
%Authorization{} = auth <- Repo.get_by(Authorization, token: params["code"], app_id: app.id), | |||
{:ok, token} <- Token.exchange_token(app, auth) do | |||
response = %{ | |||
token_type: "Bearer", | |||
access_token: token.token, | |||
refresh_token: token.refresh_token, | |||
expires_in: 60 * 10, | |||
scope: "read write follow" | |||
} | |||
json(conn, response) | |||
end | |||
end | |||
end |
@@ -0,0 +1,4 @@ | |||
defmodule Pleroma.Web.OAuth.OAuthView do | |||
use Pleroma.Web, :view | |||
import Phoenix.HTML.Form | |||
end |
@@ -0,0 +1,38 @@ | |||
defmodule Pleroma.Web.OAuth.Token do | |||
use Ecto.Schema | |||
alias Pleroma.{User, Repo} | |||
alias Pleroma.Web.OAuth.{Token, App, Authorization} | |||
schema "oauth_tokens" do | |||
field :token, :string | |||
field :refresh_token, :string | |||
field :valid_until, :naive_datetime | |||
belongs_to :user, Pleroma.User | |||
belongs_to :app, Pleroma.App | |||
timestamps() | |||
end | |||
def exchange_token(app, auth) do | |||
with {:ok, auth} <- Authorization.use_token(auth), | |||
true <- auth.app_id == app.id do | |||
create_token(app, Repo.get(User, auth.user_id)) | |||
end | |||
end | |||
def create_token(%App{} = app, %User{} = user) do | |||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 | |||
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64 | |||
token = %Token{ | |||
token: token, | |||
refresh_token: refresh_token, | |||
user_id: user.id, | |||
app_id: app.id, | |||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, 60 * 10) | |||
} | |||
Repo.insert(token) | |||
end | |||
end |
@@ -10,12 +10,14 @@ defmodule Pleroma.Web.Router do | |||
pipeline :api do | |||
plug :accepts, ["json"] | |||
plug :fetch_session | |||
plug Pleroma.Plugs.OAuthPlug | |||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1, optional: true} | |||
end | |||
pipeline :authenticated_api do | |||
plug :accepts, ["json"] | |||
plug :fetch_session | |||
plug Pleroma.Plugs.OAuthPlug | |||
plug Pleroma.Plugs.AuthenticationPlug, %{fetcher: &Router.user_fetcher/1} | |||
end | |||
@@ -27,14 +29,43 @@ defmodule Pleroma.Web.Router do | |||
plug :accepts, ["json", "xml"] | |||
end | |||
pipeline :masto_config do | |||
plug :accepts, ["json"] | |||
pipeline :oauth do | |||
plug :accepts, ["html", "json"] | |||
end | |||
scope "/oauth", Pleroma.Web.OAuth do | |||
get "/authorize", OAuthController, :authorize | |||
post "/authorize", OAuthController, :create_authorization | |||
post "/token", OAuthController, :token_exchange | |||
end | |||
scope "/api/v1", Pleroma.Web.MastodonAPI do | |||
pipe_through :api | |||
get "/instance", MastodonAPIController, :masto_instance | |||
post "/apps", MastodonAPIController, :create_app | |||
get "/timelines/public", MastodonAPIController, :public_timeline | |||
get "/statuses/:id", MastodonAPIController, :get_status | |||
get "/statuses/:id/context", MastodonAPIController, :get_context | |||
get "/accounts/:id/statuses", MastodonAPIController, :user_statuses | |||
end | |||
scope "/api/v1", Pleroma.Web do | |||
pipe_through :masto_config | |||
# TODO: Move this | |||
get "/instance", TwitterAPI.UtilController, :masto_instance | |||
scope "/api/v1", Pleroma.Web.MastodonAPI do | |||
pipe_through :authenticated_api | |||
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials | |||
get "/timelines/home", MastodonAPIController, :home_timeline | |||
post "/statuses", MastodonAPIController, :post_status | |||
delete "/statuses/:id", MastodonAPIController, :delete_status | |||
post "/statuses/:id/reblog", MastodonAPIController, :reblog_status | |||
post "/statuses/:id/favourite", MastodonAPIController, :fav_status | |||
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status | |||
get "/notifications", MastodonAPIController, :notifications | |||
end | |||
scope "/api", Pleroma.Web do | |||
@@ -0,0 +1,11 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset=utf-8 /> | |||
<title>Pleroma</title> | |||
</head> | |||
<body> | |||
<h1>Welcome to Pleroma</h1> | |||
<%= render @view_module, @view_template, assigns %> | |||
</body> | |||
</html> |
@@ -0,0 +1,2 @@ | |||
<h1>Successfully authorized</h1> | |||
<h2>Token code is <%= @auth.token %></h2> |
@@ -0,0 +1,14 @@ | |||
<h2>OAuth Authorization</h2> | |||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> | |||
<%= label f, :name, "Name" %> | |||
<%= text_input f, :name %> | |||
<br> | |||
<%= label f, :password, "Password" %> | |||
<%= password_input f, :password %> | |||
<br> | |||
<%= hidden_input f, :client_id, value: @client_id %> | |||
<%= hidden_input f, :response_type, value: @response_type %> | |||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %> | |||
<%= hidden_input f, :scope, value: @scope %> | |||
<%= submit "Authorize" %> | |||
<% end %> |
@@ -42,16 +42,4 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do | |||
_ -> json(conn, "Pleroma Dev") | |||
end | |||
end | |||
# TODO: Move this | |||
def masto_instance(conn, _params) do | |||
response = %{ | |||
uri: Web.base_url, | |||
title: Web.base_url, | |||
description: "A Pleroma instance, an alternative fediverse server", | |||
version: "dev" | |||
} | |||
json(conn, response) | |||
end | |||
end |
@@ -3,7 +3,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter | |||
alias Pleroma.Web.TwitterAPI.UserView | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Web.{OStatus, CommonAPI} | |||
alias Pleroma.Formatter | |||
import Pleroma.Web.TwitterAPI.Utils | |||
@@ -115,43 +115,28 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do | |||
end | |||
end | |||
def favorite(%User{} = user, %Activity{data: %{"object" => object}} = activity) do | |||
object = Object.get_by_ap_id(object["id"]) | |||
{:ok, _like_activity, object} = ActivityPub.like(user, object) | |||
new_data = activity.data | |||
|> Map.put("object", object.data) | |||
status = %{activity | data: new_data} | |||
|> activity_to_status(%{for: user}) | |||
{:ok, status} | |||
def repeat(%User{} = user, ap_id_or_id) do | |||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user), | |||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), | |||
status <- activity_to_status(activity, %{for: user}) do | |||
{:ok, status} | |||
end | |||
end | |||
def unfavorite(%User{} = user, %Activity{data: %{"object" => object}} = activity) do | |||
object = Object.get_by_ap_id(object["id"]) | |||
{:ok, object} = ActivityPub.unlike(user, object) | |||
new_data = activity.data | |||
|> Map.put("object", object.data) | |||
status = %{activity | data: new_data} | |||
|> activity_to_status(%{for: user}) | |||
{:ok, status} | |||
def fav(%User{} = user, ap_id_or_id) do | |||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), | |||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), | |||
status <- activity_to_status(activity, %{for: user}) do | |||
{:ok, status} | |||
end | |||
end | |||
def retweet(%User{} = user, %Activity{data: %{"object" => object}} = activity) do | |||
object = Object.get_by_ap_id(object["id"]) | |||
{:ok, _announce_activity, object} = ActivityPub.announce(user, object) | |||
new_data = activity.data | |||
|> Map.put("object", object.data) | |||
status = %{activity | data: new_data} | |||
|> activity_to_status(%{for: user}) | |||
{:ok, status} | |||
def unfav(%User{} = user, ap_id_or_id) do | |||
with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), | |||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), | |||
status <- activity_to_status(activity, %{for: user}) do | |||
{:ok, status} | |||
end | |||
end | |||
def upload(%Plug.Upload{} = file, format \\ "xml") do | |||
@@ -2,6 +2,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} | |||
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.{Repo, Activity, User, Object} | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Ecto.Changeset | |||
@@ -95,10 +96,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
end | |||
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, id), | |||
%Object{} = object <- Object.get_by_ap_id(object_id), | |||
true <- user.ap_id == object.data["actor"], | |||
{:ok, delete} <- ActivityPub.delete(object) |> IO.inspect do | |||
with {:ok, delete} <- CommonAPI.delete(id, user) do | |||
json = ActivityRepresenter.to_json(delete, %{user: user, for: user}) | |||
conn | |||
|> json_reply(200, json) | |||
@@ -151,40 +149,25 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
end | |||
def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
activity = get_by_id_or_ap_id(id) | |||
{:ok, status} = TwitterAPI.favorite(user, activity) | |||
response = Poison.encode!(status) | |||
conn | |||
|> json_reply(200, response) | |||
with {:ok, status} <- TwitterAPI.fav(user, id) do | |||
json(conn, status) | |||
end | |||
end | |||
def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
activity = get_by_id_or_ap_id(id) | |||
{:ok, status} = TwitterAPI.unfavorite(user, activity) | |||
response = Poison.encode!(status) | |||
conn | |||
|> json_reply(200, response) | |||
with {:ok, status} <- TwitterAPI.unfav(user, id) do | |||
json(conn, status) | |||
end | |||
end | |||
def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do | |||
activity = get_by_id_or_ap_id(id) | |||
if activity.data["actor"] == user.ap_id do | |||
bad_request_reply(conn, "You cannot repeat your own notice.") | |||
else | |||
{:ok, status} = TwitterAPI.retweet(user, activity) | |||
response = Poison.encode!(status) | |||
conn | |||
|> json_reply(200, response) | |||
with {:ok, status} <- TwitterAPI.repeat(user, id) do | |||
json(conn, status) | |||
end | |||
end | |||
def register(conn, params) do | |||
with {:ok, user} <- TwitterAPI.register_user(params) do | |||
render(conn, UserView, "show.json", %{user: user}) | |||
else | |||
{:error, errors} -> | |||
@@ -0,0 +1,3 @@ | |||
defmodule Pleroma.Web.LayoutView do | |||
use Pleroma.Web, :view | |||
end |
@@ -28,7 +28,7 @@ defmodule Pleroma.Mixfile do | |||
# | |||
# Type `mix help deps` for examples and options. | |||
defp deps do | |||
[{:phoenix, "~> 1.3.0-rc"}, | |||
[{:phoenix, "~> 1.3.0"}, | |||
{:phoenix_pubsub, "~> 1.0"}, | |||
{:phoenix_ecto, "~> 3.2"}, | |||
{:postgrex, ">= 0.0.0"}, | |||
@@ -37,12 +37,12 @@ defmodule Pleroma.Mixfile do | |||
{:comeonin, "~> 3.0"}, | |||
{:trailing_format_plug, "~> 0.0.5" }, | |||
{:html_sanitize_ex, "~> 1.3.0-rc1"}, | |||
{:phoenix_html, "~> 2.10"}, | |||
{:calendar, "~> 0.16.1"}, | |||
{:cachex, "~> 2.1"}, | |||
{:httpoison, "~> 0.11.2"}, | |||
{:ex_machina, "~> 2.0", only: :test}, | |||
{:credo, "~> 0.7", only: [:dev, :test]}, | |||
{:mix_test_watch, "~> 0.2", only: :dev}] | |||
{:credo, "~> 0.7", only: [:dev, :test]}] | |||
end | |||
# Aliases are shortcuts or tasks specific to the current project. | |||
@@ -27,10 +27,11 @@ | |||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, | |||
"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], []}, | |||
"phoenix": {:hex, :phoenix, "1.3.0-rc.1", "0d04948a4bd24823f101024c07b6a4d35e58f1fd92a465c1bc75dd37acd1041a", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: false]}]}, | |||
"phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: false]}]}, | |||
"phoenix_ecto": {:hex, :phoenix_ecto, "3.2.3", "450c749876ff1de4a78fdb305a142a76817c77a1cd79aeca29e5fc9a6c630b26", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]}, | |||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []}, | |||
"plug": {:hex, :plug, "1.3.4", "b4ef3a383f991bfa594552ded44934f2a9853407899d47ecc0481777fb1906f6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, | |||
"phoenix_html": {:hex, :phoenix_html, "2.10.4", "d4f99c32d5dc4918b531fdf163e1fd7cf20acdd7703f16f5d02d4db36de803b7", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, | |||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], []}, | |||
"plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, | |||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, | |||
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, | |||
"postgrex": {:hex, :postgrex, "0.13.2", "2b88168fc6a5456a27bfb54ccf0ba4025d274841a7a3af5e5deb1b755d95154e", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]}, | |||
@@ -0,0 +1,16 @@ | |||
defmodule Pleroma.Repo.Migrations.AddMastodonApps do | |||
use Ecto.Migration | |||
def change do | |||
create table(:apps) do | |||
add :client_name, :string | |||
add :redirect_uris, :string | |||
add :scopes, :string | |||
add :website, :string | |||
add :client_id, :string | |||
add :client_secret, :string | |||
timestamps() | |||
end | |||
end | |||
end |
@@ -0,0 +1,15 @@ | |||
defmodule Pleroma.Repo.Migrations.CreateOAuthAuthorizations do | |||
use Ecto.Migration | |||
def change do | |||
create table(:oauth_authorizations) do | |||
add :app_id, references(:apps) | |||
add :user_id, references(:users) | |||
add :token, :string | |||
add :valid_until, :naive_datetime | |||
add :used, :boolean, default: false | |||
timestamps() | |||
end | |||
end | |||
end |
@@ -0,0 +1,15 @@ | |||
defmodule Pleroma.Repo.Migrations.CreateOAuthToken do | |||
use Ecto.Migration | |||
def change do | |||
create table(:oauth_tokens) do | |||
add :app_id, references(:apps) | |||
add :user_id, references(:users) | |||
add :token, :string | |||
add :refresh_token, :string | |||
add :valid_until, :naive_datetime | |||
timestamps() | |||
end | |||
end | |||
end |
@@ -0,0 +1,15 @@ | |||
defmodule Pleroma.Repo.Migrations.CreateNotifications do | |||
use Ecto.Migration | |||
def change do | |||
create table(:notifications) do | |||
add :user_id, references(:users, on_delete: :delete_all) | |||
add :activity_id, references(:activities, on_delete: :delete_all) | |||
add :seen, :boolean, default: false | |||
timestamps() | |||
end | |||
create index(:notifications, [:user_id]) | |||
end | |||
end |
@@ -0,0 +1,23 @@ | |||
defmodule Pleroma.NotificationTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.{User, Notification} | |||
import Pleroma.Factory | |||
describe "create_notifications" do | |||
test "notifies someone when they are directly addressed" do | |||
user = insert(:user) | |||
other_user = insert(:user) | |||
third_user = insert(:user) | |||
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname} and @#{third_user.nickname}"}) | |||
{:ok, [notification, other_notification]} = Notification.create_notifications(activity) | |||
assert notification.user_id == other_user.id | |||
assert notification.activity_id == activity.id | |||
assert other_notification.user_id == third_user.id | |||
assert other_notification.activity_id == activity.id | |||
end | |||
end | |||
end |
@@ -0,0 +1,42 @@ | |||
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do | |||
use Pleroma.DataCase | |||
import Pleroma.Factory | |||
alias Pleroma.Web.MastodonAPI.AccountView | |||
test "Represent a user account" do | |||
user = insert(:user, %{info: %{"note_count" => 5, "follower_count" => 3}}) | |||
expected = %{ | |||
id: user.id, | |||
username: user.nickname, | |||
acct: user.nickname, | |||
display_name: user.name, | |||
locked: false, | |||
created_at: user.inserted_at, | |||
followers_count: 3, | |||
following_count: 0, | |||
statuses_count: 5, | |||
note: user.bio, | |||
url: user.ap_id, | |||
avatar: "https://placehold.it/48x48", | |||
avatar_static: "https://placehold.it/48x48", | |||
header: "https://placehold.it/700x335", | |||
header_static: "https://placehold.it/700x335" | |||
} | |||
assert expected == AccountView.render("account.json", %{user: user}) | |||
end | |||
test "Represent a smaller mention" do | |||
user = insert(:user) | |||
expected = %{ | |||
id: user.id, | |||
acct: user.nickname, | |||
username: user.nickname, | |||
url: user.ap_id | |||
} | |||
assert expected == AccountView.render("mention.json", %{user: user}) | |||
end | |||
end |
@@ -0,0 +1,184 @@ | |||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
use Pleroma.Web.ConnCase | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.{Repo, User, Activity} | |||
alias Pleroma.Web.{OStatus, CommonAPI} | |||
import Pleroma.Factory | |||
test "the home timeline", %{conn: conn} do | |||
user = insert(:user) | |||
following = insert(:user) | |||
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"}) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> get("/api/v1/timelines/home") | |||
assert length(json_response(conn, 200)) == 0 | |||
{:ok, user} = User.follow(user, following) | |||
conn = build_conn() | |||
|> assign(:user, user) | |||
|> get("/api/v1/timelines/home") | |||
assert [%{"content" => "test"}] = json_response(conn, 200) | |||
end | |||
test "the public timeline", %{conn: conn} do | |||
following = insert(:user) | |||
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"}) | |||
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") | |||
conn = conn | |||
|> get("/api/v1/timelines/public") | |||
assert length(json_response(conn, 200)) == 2 | |||
conn = build_conn() | |||
|> get("/api/v1/timelines/public", %{"local" => "True"}) | |||
assert [%{"content" => "test"}] = json_response(conn, 200) | |||
end | |||
test "posting a status", %{conn: conn} do | |||
user = insert(:user) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses", %{"status" => "cofe"}) | |||
assert %{"content" => "cofe", "id" => id} = json_response(conn, 200) | |||
assert Repo.get(Activity, id) | |||
end | |||
test "replying to a status", %{conn: conn} do | |||
user = insert(:user) | |||
{:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"}) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) | |||
assert %{"content" => "xD", "id" => id} = json_response(conn, 200) | |||
activity = Repo.get(Activity, id) | |||
assert activity.data["context"] == replied_to.data["context"] | |||
assert activity.data["object"]["inReplyToStatusId"] == replied_to.id | |||
end | |||
test "verify_credentials", %{conn: conn} do | |||
user = insert(:user) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> get("/api/v1/accounts/verify_credentials") | |||
assert %{"id" => id} = json_response(conn, 200) | |||
assert id == user.id | |||
end | |||
test "get a status", %{conn: conn} do | |||
activity = insert(:note_activity) | |||
conn = conn | |||
|> get("/api/v1/statuses/#{activity.id}") | |||
assert %{"id" => id} = json_response(conn, 200) | |||
assert id == activity.id | |||
end | |||
describe "deleting a status" do | |||
test "when you created it", %{conn: conn} do | |||
activity = insert(:note_activity) | |||
author = User.get_by_ap_id(activity.data["actor"]) | |||
conn = conn | |||
|> assign(:user, author) | |||
|> delete("/api/v1/statuses/#{activity.id}") | |||
assert %{} = json_response(conn, 200) | |||
assert Repo.get(Activity, activity.id) == nil | |||
end | |||
test "when you didn't create it", %{conn: conn} do | |||
activity = insert(:note_activity) | |||
user = insert(:user) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> delete("/api/v1/statuses/#{activity.id}") | |||
assert %{"error" => _} = json_response(conn, 403) | |||
assert Repo.get(Activity, activity.id) == activity | |||
end | |||
end | |||
describe "reblogging" do | |||
test "reblogs and returns the reblogged status", %{conn: conn} do | |||
activity = insert(:note_activity) | |||
user = insert(:user) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses/#{activity.id}/reblog") | |||
assert %{"id" => id, "reblogged" => true, "reblogs_count" => 1} = json_response(conn, 200) | |||
assert activity.id == id | |||
end | |||
end | |||
describe "favoriting" do | |||
test "favs a status and returns it", %{conn: conn} do | |||
activity = insert(:note_activity) | |||
user = insert(:user) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses/#{activity.id}/favourite") | |||
assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = json_response(conn, 200) | |||
assert activity.id == id | |||
end | |||
end | |||
describe "unfavoriting" do | |||
test "unfavorites a status and returns it", %{conn: conn} do | |||
activity = insert(:note_activity) | |||
user = insert(:user) | |||
{:ok, _, _} = CommonAPI.favorite(activity.id, user) | |||
conn = conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/statuses/#{activity.id}/unfavourite") | |||
assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = json_response(conn, 200) | |||
assert activity.id == id | |||
end | |||
end | |||
describe "user timelines" do | |||
test "gets a users statuses", %{conn: conn} do | |||
_note = insert(:note_activity) | |||
note_two = insert(:note_activity) | |||
user = User.get_by_ap_id(note_two.data["actor"]) | |||
conn = conn | |||
|> get("/api/v1/accounts/#{user.id}/statuses") | |||
assert [%{"id" => id}] = json_response(conn, 200) | |||
assert id == note_two.id | |||
end | |||
end | |||
end |
@@ -0,0 +1,77 @@ | |||
defmodule Pleroma.Web.MastodonAPI.StatusViewTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView} | |||
alias Pleroma.{User, Object} | |||
alias Pleroma.Web.OStatus | |||
import Pleroma.Factory | |||
test "a note activity" do | |||
note = insert(:note_activity) | |||
user = User.get_cached_by_ap_id(note.data["actor"]) | |||
status = StatusView.render("status.json", %{activity: note}) | |||
expected = %{ | |||
id: note.id, | |||
uri: note.data["object"]["id"], | |||
url: note.data["object"]["external_id"], | |||
account: AccountView.render("account.json", %{user: user}), | |||
in_reply_to_id: nil, | |||
in_reply_to_account_id: nil, | |||
reblog: nil, | |||
content: HtmlSanitizeEx.basic_html(note.data["object"]["content"]), | |||
created_at: note.data["object"]["published"], | |||
reblogs_count: 0, | |||
favourites_count: 0, | |||
reblogged: false, | |||
favourited: false, | |||
muted: false, | |||
sensitive: false, | |||
spoiler_text: "", | |||
visibility: "public", | |||
media_attachments: [], | |||
mentions: [], | |||
tags: [], | |||
application: nil, | |||
language: nil | |||
} | |||
assert status == expected | |||
end | |||
test "contains mentions" do | |||
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") | |||
user = insert(:user, %{ap_id: "https://pleroma.soykaf.com/users/lain"}) | |||
{:ok, [activity]} = OStatus.handle_incoming(incoming) | |||
status = StatusView.render("status.json", %{activity: activity}) | |||
assert status.mentions == [AccountView.render("mention.json", %{user: user})] | |||
end | |||
test "attachments" do | |||
incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") | |||
object = %{ | |||
"type" => "Image", | |||
"url" => [ | |||
%{ | |||
"mediaType" => "image/png", | |||
"href" => "someurl" | |||
} | |||
], | |||
"uuid" => 6 | |||
} | |||
expected = %{ | |||
id: 6, | |||
type: "image", | |||
url: "someurl", | |||
remote_url: "someurl", | |||
preview_url: "someurl" | |||
} | |||
assert expected == StatusView.render("attachment.json", %{attachment: object}) | |||
end | |||
end |
@@ -0,0 +1,42 @@ | |||
defmodule Pleroma.Web.OAuth.AuthorizationTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.OAuth.{Authorization, App} | |||
import Pleroma.Factory | |||
test "create an authorization token for a valid app" do | |||
{:ok, app} = Repo.insert(App.register_changeset(%App{}, %{client_name: "client", scopes: "scope", redirect_uris: "url"})) | |||
user = insert(:user) | |||
{:ok, auth} = Authorization.create_authorization(app, user) | |||
assert auth.user_id == user.id | |||
assert auth.app_id == app.id | |||
assert String.length(auth.token) > 10 | |||
assert auth.used == false | |||
end | |||
test "use up a token" do | |||
{:ok, app} = Repo.insert(App.register_changeset(%App{}, %{client_name: "client", scopes: "scope", redirect_uris: "url"})) | |||
user = insert(:user) | |||
{:ok, auth} = Authorization.create_authorization(app, user) | |||
{:ok, auth} = Authorization.use_token(auth) | |||
assert auth.used == true | |||
assert {:error, "already used"} == Authorization.use_token(auth) | |||
expired_auth = %Authorization{ | |||
user_id: user.id, | |||
app_id: app.id, | |||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now, -10), | |||
token: "mytoken", | |||
used: false | |||
} | |||
{:ok, expired_auth} = Repo.insert(expired_auth) | |||
assert {:error, "token expired"} == Authorization.use_token(expired_auth) | |||
end | |||
end |
@@ -0,0 +1,24 @@ | |||
defmodule Pleroma.Web.OAuth.TokenTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.OAuth.{App, Token, Authorization} | |||
alias Pleroma.Repo | |||
import Pleroma.Factory | |||
test "exchanges a auth token for an access token" do | |||
{:ok, app} = Repo.insert(App.register_changeset(%App{}, %{client_name: "client", scopes: "scope", redirect_uris: "url"})) | |||
user = insert(:user) | |||
{:ok, auth} = Authorization.create_authorization(app, user) | |||
{:ok, token} = Token.exchange_token(app, auth) | |||
assert token.app_id == app.id | |||
assert token.user_id == user.id | |||
assert String.length(token.token) > 10 | |||
assert String.length(token.refresh_token) > 10 | |||
auth = Repo.get(Authorization, auth.id) | |||
{:error, "already used"} = Token.exchange_token(app, auth) | |||
end | |||
end |
@@ -354,13 +354,6 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
request_path = "/api/statuses/retweet/#{note_activity.id}.json" | |||
user = Repo.get_by(User, ap_id: note_activity.data["actor"]) | |||
response = conn | |||
|> with_credentials(user.nickname, "test") | |||
|> post(request_path) | |||
assert json_response(response, 400) == %{"error" => "You cannot repeat your own notice.", | |||
"request" => request_path} | |||
response = conn | |||
|> with_credentials(current_user.nickname, "test") | |||
|> post(request_path) | |||
@@ -264,7 +264,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do | |||
note_activity = insert(:note_activity) | |||
activity_user = Repo.get_by!(User, ap_id: note_activity.data["actor"]) | |||
{:ok, status} = TwitterAPI.favorite(user, note_activity) | |||
{:ok, status} = TwitterAPI.fav(user, note_activity.id) | |||
updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) | |||
assert status == ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user}) | |||
@@ -280,7 +280,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do | |||
updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) | |||
assert ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user})["fave_num"] == 1 | |||
{:ok, status} = TwitterAPI.unfavorite(user, note_activity) | |||
{:ok, status} = TwitterAPI.unfav(user, note_activity.id) | |||
assert status["fave_num"] == 0 | |||
end | |||
@@ -290,7 +290,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do | |||
note_activity = insert(:note_activity) | |||
activity_user = Repo.get_by!(User, ap_id: note_activity.data["actor"]) | |||
{:ok, status} = TwitterAPI.retweet(user, note_activity) | |||
{:ok, status} = TwitterAPI.repeat(user, note_activity.id) | |||
updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) | |||
assert status == ActivityRepresenter.to_map(updated_activity, %{user: activity_user, for: user}) | |||