Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into develop

This commit is contained in:
Roger Braun 2017-11-12 14:23:39 +01:00
commit 5fc6e9d467
7 changed files with 347 additions and 24 deletions

View file

@ -36,6 +36,37 @@ def for_user(user, opts \\ %{}) do
Repo.all(query) Repo.all(query)
end end
def get(%{id: user_id} = _user, id) do
query = from n in Notification,
where: n.id == ^id,
preload: [:activity]
notification = Repo.one(query)
case notification do
%{user_id: ^user_id} ->
{:ok, notification}
_ ->
{:error, "Cannot get notification"}
end
end
def clear(user) do
query = from n in Notification,
where: n.user_id == ^user.id
Repo.delete_all(query)
end
def dismiss(%{id: user_id} = _user, id) do
notification = Repo.get(Notification, id)
case notification do
%{user_id: ^user_id} ->
Repo.delete(notification)
_ ->
{:error, "Cannot dismiss notification"}
end
end
def create_notifications(%Activity{id: id, data: %{"to" => to, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do 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) users = User.get_notified_from_activity(activity)

View file

@ -9,9 +9,8 @@ def store(%Plug.Upload{} = file) do
File.cp!(file.path, result_file) File.cp!(file.path, result_file)
# fix content type on some image uploads # fix content type on some image uploads
matches = Regex.named_captures(~r/\.(?<ext>(jpg|jpeg|png|gif))$/i, file.filename) content_type = if file.content_type == "application/octet-stream" do
content_type = if file.content_type == "application/octet-stream" and matches do get_content_type(file.path)
if matches["ext"] == "jpg", do: "image/jpeg", else: "image/#{matches["ext"]}"
else else
file.content_type file.content_type
end end
@ -61,4 +60,34 @@ defp upload_path do
defp url_for(file) do defp url_for(file) do
"#{Web.base_url()}/media/#{file}" "#{Web.base_url()}/media/#{file}"
end end
def get_content_type(file) do
match = File.open(file, [:read], fn(f) ->
case IO.binread(f, 8) do
<<0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a>> ->
"image/png"
<<0x47, 0x49, 0x46, 0x38, _, 0x61, _, _>> ->
"image/gif"
<<0xff, 0xd8, 0xff, _, _, _, _, _>> ->
"image/jpeg"
<<0x1a, 0x45, 0xdf, 0xa3, _, _, _, _>> ->
"video/webm"
<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70>> ->
"video/mp4"
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
"audio/mpeg"
<<0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
"audio/ogg"
<<0x52, 0x49, 0x46, 0x46, _, _, _, _>> ->
"audio/wav"
_ ->
"application/octet-stream"
end
end)
case match do
{:ok, type} -> type
_e -> "application/octet-stream"
end
end
end end

View file

@ -24,6 +24,57 @@ def create_app(conn, params) do
end end
end end
def update_credentials(%{assigns: %{user: user}} = conn, params) do
params = if bio = params["note"] do
Map.put(params, "bio", bio)
else
params
end
params = if name = params["display_name"] do
Map.put(params, "name", name)
else
params
end
user = if avatar = params["avatar"] do
with %Plug.Upload{} <- avatar,
{:ok, object} <- ActivityPub.upload(avatar),
change = Ecto.Changeset.change(user, %{avatar: object.data}),
{:ok, user} = Repo.update(change) do
user
else
_e -> user
end
else
user
end
user = if banner = params["header"] do
with %Plug.Upload{} <- banner,
{:ok, object} <- ActivityPub.upload(banner),
new_info <- Map.put(user.info, "banner", object.data),
change <- User.info_changeset(user, %{info: new_info}),
{:ok, user} <- Repo.update(change) do
user
else
_e -> user
end
else
user
end
with changeset <- User.update_changeset(user, params),
{:ok, user} <- Repo.update(changeset) do
json conn, AccountView.render("account.json", %{user: user})
else
_e ->
conn
|> put_status(403)
|> json(%{error: "Invalid request"})
end
end
def verify_credentials(%{assigns: %{user: user}} = conn, params) do def verify_credentials(%{assigns: %{user: user}} = conn, params) do
account = AccountView.render("account.json", %{user: user}) account = AccountView.render("account.json", %{user: user})
json(conn, account) json(conn, account)
@ -194,23 +245,8 @@ def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
def notifications(%{assigns: %{user: user}} = conn, params) do def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params) notifications = Notification.for_user(user, params)
result = Enum.map(notifications, fn (%{id: id, activity: activity, inserted_at: created_at}) -> result = Enum.map(notifications, fn x ->
actor = User.get_cached_by_ap_id(activity.data["actor"]) render_notification(user, x)
created_at = NaiveDateTime.to_iso8601(created_at)
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
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, for: user})}
"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, for: user})}
"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, for: user})}
"Follow" ->
%{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
_ -> nil
end
end) end)
|> Enum.filter(&(&1)) |> Enum.filter(&(&1))
@ -219,6 +255,33 @@ def notifications(%{assigns: %{user: user}} = conn, params) do
|> json(result) |> json(result)
end end
def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
with {:ok, notification} <- Notification.get(user, id) do
json(conn, render_notification(user, notification))
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Poison.encode!(%{"error" => reason}))
end
end
def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
Notification.clear(user)
json(conn, %{})
end
def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
with {:ok, _notif} <- Notification.dismiss(user, id) do
json(conn, %{})
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Poison.encode!(%{"error" => reason}))
end
end
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
id = List.wrap(id) id = List.wrap(id)
q = from u in User, q = from u in User,
@ -527,4 +590,23 @@ def empty_array(conn, _) do
Logger.debug("Unimplemented, returning an empty array") Logger.debug("Unimplemented, returning an empty array")
json(conn, []) json(conn, [])
end end
defp render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
actor = User.get_cached_by_ap_id(activity.data["actor"])
created_at = NaiveDateTime.to_iso8601(created_at)
|> String.replace(~r/(\.\d+)?$/, ".000Z", global: false)
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, for: user})}
"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, for: user})}
"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, for: user})}
"Follow" ->
%{id: id, type: "follow", created_at: created_at, account: AccountView.render("account.json", %{user: actor})}
_ -> nil
end
end
end end

View file

@ -60,6 +60,7 @@ def user_fetcher(username) do
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through :authenticated_api pipe_through :authenticated_api
patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
get "/accounts/relationships", MastodonAPIController, :relationships get "/accounts/relationships", MastodonAPIController, :relationships
get "/accounts/search", MastodonAPIController, :account_search get "/accounts/search", MastodonAPIController, :account_search
@ -89,7 +90,10 @@ def user_fetcher(username) do
post "/statuses/:id/favourite", MastodonAPIController, :fav_status post "/statuses/:id/favourite", MastodonAPIController, :fav_status
post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status post "/statuses/:id/unfavourite", MastodonAPIController, :unfav_status
post "/notifications/clear", MastodonAPIController, :clear_notifications
post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
get "/notifications", MastodonAPIController, :notifications get "/notifications", MastodonAPIController, :notifications
get "/notifications/:id", MastodonAPIController, :get_notification
post "/media", MastodonAPIController, :upload post "/media", MastodonAPIController, :upload
end end

File diff suppressed because one or more lines are too long

View file

@ -31,4 +31,65 @@ test "it doesn't create a notification for user if the user blocks the activity
assert nil == Notification.create_notification(activity, user) assert nil == Notification.create_notification(activity, user)
end end
end end
describe "get notification" do
test "it gets a notification that belongs to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.get(other_user, notification.id)
assert notification.user_id == other_user.id
end
test "it returns error if the notification doesn't belong to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:error, notification} = Notification.get(user, notification.id)
end
end
describe "dismiss notification" do
test "it dismisses a notification that belongs to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.dismiss(other_user, notification.id)
assert notification.user_id == other_user.id
end
test "it returns error if the notification doesn't belong to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:error, notification} = Notification.dismiss(user, notification.id)
end
end
describe "clear notification" do
test "it clears all notifications belonging to the user" 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, _notifs} = Notification.create_notifications(activity)
{:ok, activity} = TwitterAPI.create_status(user, %{"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !"})
{:ok, _notifs} = Notification.create_notifications(activity)
Notification.clear(other_user)
assert Notification.for_user(other_user) == []
assert Notification.for_user(third_user) != []
end
end
end end

View file

@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.{Repo, User, Activity} alias Pleroma.{Repo, User, Activity, Notification}
alias Pleroma.Web.{OStatus, CommonAPI} alias Pleroma.Web.{OStatus, CommonAPI}
import Pleroma.Factory import Pleroma.Factory
@ -122,6 +122,75 @@ test "when you didn't create it", %{conn: conn} do
end end
end end
describe "notifications" do
test "list of notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn = conn
|> assign(:user, user)
|> get("/api/v1/notifications")
expected_response = "hi <a href=\"#{user.ap_id}\">@#{user.nickname}</a>"
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
end
test "getting a single notification", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn = conn
|> assign(:user, user)
|> get("/api/v1/notifications/#{notification.id}")
expected_response = "hi <a href=\"#{user.ap_id}\">@#{user.nickname}</a>"
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response
end
test "dismissing a single notification", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn = conn
|> assign(:user, user)
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
assert %{} = json_response(conn, 200)
end
test "clearing all notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn = conn
|> assign(:user, user)
|> post("/api/v1/notifications/clear")
assert %{} = json_response(conn, 200)
conn = build_conn()
|> assign(:user, user)
|> get("/api/v1/notifications")
assert all = json_response(conn, 200)
assert all == []
end
end
describe "reblogging" do describe "reblogging" do
test "reblogs and returns the reblogged status", %{conn: conn} do test "reblogs and returns the reblogged status", %{conn: conn} do
activity = insert(:note_activity) activity = insert(:note_activity)
@ -420,4 +489,54 @@ test "returns the favorites of a user", %{conn: conn} do
assert [status] = json_response(conn, 200) assert [status] = json_response(conn, 200)
assert status["id"] == to_string(activity.id) assert status["id"] == to_string(activity.id)
end end
describe "updating credentials" do
test "updates the user's bio" do
user = insert(:user)
conn = conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"note" => "I drink #cofe"})
assert user = json_response(conn, 200)
assert user["note"] == "I drink #cofe"
end
test "updates the user's name" do
user = insert(:user)
conn = conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
assert user = json_response(conn, 200)
assert user["display_name"] == "markorepairs"
end
test "updates the user's avatar" do
user = insert(:user)
new_avatar = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
conn = conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
assert user = json_response(conn, 200)
assert user["avatar"] != "https://placehold.it/48x48"
end
test "updates the user's banner" do
user = insert(:user)
new_header = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
conn = conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
assert user = json_response(conn, 200)
assert user["header"] != "https://placehold.it/700x335"
end
end
end end