forked from AkkomaGang/akkoma
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into develop
This commit is contained in:
commit
5fc6e9d467
7 changed files with 347 additions and 24 deletions
|
@ -36,6 +36,37 @@ def for_user(user, opts \\ %{}) do
|
|||
Repo.all(query)
|
||||
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
|
||||
users = User.get_notified_from_activity(activity)
|
||||
|
||||
|
|
|
@ -9,9 +9,8 @@ def store(%Plug.Upload{} = file) do
|
|||
File.cp!(file.path, result_file)
|
||||
|
||||
# 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" and matches do
|
||||
if matches["ext"] == "jpg", do: "image/jpeg", else: "image/#{matches["ext"]}"
|
||||
content_type = if file.content_type == "application/octet-stream" do
|
||||
get_content_type(file.path)
|
||||
else
|
||||
file.content_type
|
||||
end
|
||||
|
@ -61,4 +60,34 @@ defp upload_path do
|
|||
defp url_for(file) do
|
||||
"#{Web.base_url()}/media/#{file}"
|
||||
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
|
||||
|
|
|
@ -24,6 +24,57 @@ def create_app(conn, params) do
|
|||
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
|
||||
account = AccountView.render("account.json", %{user: user})
|
||||
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
|
||||
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"])
|
||||
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
|
||||
result = Enum.map(notifications, fn x ->
|
||||
render_notification(user, x)
|
||||
end)
|
||||
|> Enum.filter(&(&1))
|
||||
|
||||
|
@ -219,6 +255,33 @@ def notifications(%{assigns: %{user: user}} = conn, params) do
|
|||
|> json(result)
|
||||
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
|
||||
id = List.wrap(id)
|
||||
q = from u in User,
|
||||
|
@ -527,4 +590,23 @@ def empty_array(conn, _) do
|
|||
Logger.debug("Unimplemented, returning an empty array")
|
||||
json(conn, [])
|
||||
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
|
||||
|
|
|
@ -60,6 +60,7 @@ def user_fetcher(username) do
|
|||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
pipe_through :authenticated_api
|
||||
|
||||
patch "/accounts/update_credentials", MastodonAPIController, :update_credentials
|
||||
get "/accounts/verify_credentials", MastodonAPIController, :verify_credentials
|
||||
get "/accounts/relationships", MastodonAPIController, :relationships
|
||||
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/unfavourite", MastodonAPIController, :unfav_status
|
||||
|
||||
post "/notifications/clear", MastodonAPIController, :clear_notifications
|
||||
post "/notifications/dismiss", MastodonAPIController, :dismiss_notification
|
||||
get "/notifications", MastodonAPIController, :notifications
|
||||
get "/notifications/:id", MastodonAPIController, :get_notification
|
||||
|
||||
post "/media", MastodonAPIController, :upload
|
||||
end
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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)
|
||||
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
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
|||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
alias Pleroma.{Repo, User, Activity}
|
||||
alias Pleroma.{Repo, User, Activity, Notification}
|
||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||
|
||||
import Pleroma.Factory
|
||||
|
@ -122,6 +122,75 @@ test "when you didn't create it", %{conn: conn} do
|
|||
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
|
||||
test "reblogs and returns the reblogged status", %{conn: conn} do
|
||||
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["id"] == to_string(activity.id)
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue