Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
51 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 00a382f31..039cc7312 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -1,56 +1,87 @@
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
# TODO: Make generic and unify (see activity_pub.ex)
defp restrict_max(query, %{"max_id" => max_id}) do
from activity in query, where: activity.id < ^max_id
end
defp restrict_max(query, _), do: query
defp restrict_since(query, %{"since_id" => since_id}) do
from activity in query, where: activity.id > ^since_id
end
defp restrict_since(query, _), do: query
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
query = query
|> restrict_since(opts)
|> restrict_max(opts)
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)
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
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) do
notification = %Notification{user_id: user.id, activity_id: activity.id}
{:ok, notification} = Repo.insert(notification)
notification
end
end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index fb06d55c1..9f50dc596 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1,470 +1,501 @@
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, OStatus}
import Ecto.Query
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 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)
end
def user(conn, %{"id" => id}) do
with %User{} = user <- Repo.get(User, id) do
account = AccountView.render("account.json", %{user: user})
json(conn, account)
else
_e -> conn
|> put_status(404)
|> json(%{error: "Can't find user"})
end
end
@instance Application.get_env(:pleroma, :instance)
def masto_instance(conn, _params) do
response = %{
uri: Web.base_url,
title: Keyword.get(@instance, :name),
description: "A Pleroma instance, an alternative fediverse server",
version: Keyword.get(@instance, :version),
email: Keyword.get(@instance, :email),
urls: %{
streaming_api: String.replace(Web.base_url, ["http","https"], "wss")
},
stats: %{
user_count: 1,
status_count: 2,
domain_count: 3
}
}
json(conn, response)
end
def custom_emojis(conn, _params) do
mastodon_emoji = Pleroma.Formatter.get_custom_emoji()
|> Enum.map(fn {shortcode, relative_url} ->
url = to_string URI.merge(Web.base_url(), relative_url)
%{
"shortcode" => shortcode,
"static_url" => url,
"url" => url
}
end)
json conn, mastodon_emoji
end
defp add_link_headers(conn, method, activities) do
last = List.last(activities)
first = List.first(activities)
if last do
min = last.id
max = first.id
next_url = mastodon_api_url(Pleroma.Web.Endpoint, method, max_id: min)
prev_url = mastodon_api_url(Pleroma.Web.Endpoint, method, since_id: max)
conn
|> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
else
conn
end
end
def home_timeline(%{assigns: %{user: user}} = conn, params) do
params = params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|> Enum.reverse
conn
|> add_link_headers(:home_timeline, activities)
|> render(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", "Announce"])
|> Map.put("local_only", !!params["local"])
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
|> Enum.reverse
conn
|> add_link_headers(:public_timeline, activities)
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
end
# TODO: Link headers
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", "Announce"])
|> Map.put("actor_id", ap_id)
|> Map.put("whole_db", true)
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"], %{"blocking_user" => user}),
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
params = params
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
{:ok, activity} = CommonAPI.post(user, params)
render conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}
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"])
- 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))
conn
|> add_link_headers(:notifications, notifications)
|> 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,
where: u.id in ^id
targets = Repo.all(q)
render conn, AccountView, "relationships.json", %{user: user, targets: targets}
end
def upload(%{assigns: %{user: user}} = conn, %{"file" => file}) do
with {:ok, object} <- ActivityPub.upload(file) do
data = object.data
|> Map.put("id", object.id)
render conn, StatusView, "attachment.json", %{attachment: data}
end
end
def favourited_by(conn, %{"id" => id}) do
with %Activity{data: %{"object" => %{"likes" => likes} = data}} <- Repo.get(Activity, id) do
q = from u in User,
where: u.ap_id in ^likes
users = Repo.all(q)
render conn, AccountView, "accounts.json", %{users: users, as: :user}
else
_ -> json(conn, [])
end
end
def reblogged_by(conn, %{"id" => id}) do
with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
q = from u in User,
where: u.ap_id in ^announces
users = Repo.all(q)
render conn, AccountView, "accounts.json", %{users: users, as: :user}
else
_ -> json(conn, [])
end
end
# TODO: Link headers
def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
params = params
|> Map.put("type", "Create")
|> Map.put("local_only", !!params["local"])
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
|> Enum.reverse
conn
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
end
# TODO: Pagination
def followers(conn, %{"id" => id}) do
with %User{} = user <- Repo.get(User, id),
{:ok, followers} <- User.get_followers(user) do
render conn, AccountView, "accounts.json", %{users: followers, as: :user}
end
end
def following(conn, %{"id" => id}) do
with %User{} = user <- Repo.get(User, id),
{:ok, followers} <- User.get_friends(user) do
render conn, AccountView, "accounts.json", %{users: followers, as: :user}
end
end
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id),
{:ok, follower} <- User.follow(follower, followed),
{:ok, activity} <- ActivityPub.follow(follower, followed) do
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
else
{:error, message} = err ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Poison.encode!(%{"error" => message}))
end
end
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
with %User{} = followed <- Repo.get_by(User, nickname: uri),
{:ok, follower} <- User.follow(follower, followed),
{:ok, activity} <- ActivityPub.follow(follower, followed) do
render conn, AccountView, "account.json", %{user: followed}
else
{:error, message} = err ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Poison.encode!(%{"error" => message}))
end
end
# TODO: Clean up and unify
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id),
{ :ok, follower, follow_activity } <- User.unfollow(follower, followed),
{ :ok, _activity } <- ActivityPub.insert(%{
"type" => "Undo",
"actor" => follower.ap_id,
"object" => follow_activity.data["id"] # get latest Follow for these users
}) do
render conn, AccountView, "relationship.json", %{user: follower, target: followed}
end
end
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
with %User{} = blocked <- Repo.get(User, id),
{:ok, blocker} <- User.block(blocker, blocked) do
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
else
{:error, message} = err ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Poison.encode!(%{"error" => message}))
end
end
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
with %User{} = blocked <- Repo.get(User, id),
{:ok, blocker} <- User.unblock(blocker, blocked) do
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
else
{:error, message} = err ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Poison.encode!(%{"error" => message}))
end
end
# TODO: Use proper query
def blocks(%{assigns: %{user: user}} = conn, _) do
with blocked_users <- user.info["blocks"] || [],
accounts <- Enum.map(blocked_users, fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) do
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
json(conn, res)
end
end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true")
fetched = if Regex.match?(~r/https?:/, query) do
with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
activities
else
_e -> []
end
end || []
q = from a in Activity,
where: fragment("?->>'type' = 'Create'", a.data),
where: fragment("to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", a.data, ^query),
limit: 20
statuses = Repo.all(q) ++ fetched
res = %{
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
"statuses" => StatusView.render("index.json", activities: statuses, for: user, as: :activity),
"hashtags" => []
}
json(conn, res)
end
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true")
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
json(conn, res)
end
def favourites(%{assigns: %{user: user}} = conn, params) do
params = conn
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_activities([], params)
|> Enum.reverse
conn
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
end
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
Logger.debug("Unimplemented, returning unmodified relationship")
with %User{} = target <- Repo.get(User, id) do
render conn, AccountView, "relationship.json", %{user: user, target: target}
end
end
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
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 4c74736e2..637c300c8 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -1,225 +1,228 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
alias Pleroma.{Repo, User, Web.Router}
def user_fetcher(username) do
{:ok, Repo.get_by(User, %{nickname: username})}
end
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
pipeline :well_known do
plug :accepts, ["xml", "xrd+xml"]
end
pipeline :config do
plug :accepts, ["json", "xml"]
end
pipeline :oauth do
plug :accepts, ["html", "json"]
end
pipeline :pleroma_api do
plug :accepts, ["html", "json"]
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through :pleroma_api
get "/password_reset/:token", UtilController, :show_password_reset
post "/password_reset", UtilController, :password_reset
get "/emoji", UtilController, :emoji
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 :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
post "/accounts/:id/follow", MastodonAPIController, :follow
post "/accounts/:id/unfollow", MastodonAPIController, :unfollow
post "/accounts/:id/block", MastodonAPIController, :block
post "/accounts/:id/unblock", MastodonAPIController, :unblock
post "/accounts/:id/mute", MastodonAPIController, :relationship_noop
post "/accounts/:id/unmute", MastodonAPIController, :relationship_noop
post "/follows", MastodonAPIController, :follow
get "/blocks", MastodonAPIController, :blocks
get "/domain_blocks", MastodonAPIController, :empty_array
get "/follow_requests", MastodonAPIController, :empty_array
get "/mutes", MastodonAPIController, :empty_array
get "/timelines/home", MastodonAPIController, :home_timeline
get "/favourites", MastodonAPIController, :favourites
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
+ 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
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through :api
get "/instance", MastodonAPIController, :masto_instance
post "/apps", MastodonAPIController, :create_app
get "/custom_emojis", MastodonAPIController, :custom_emojis
get "/timelines/public", MastodonAPIController, :public_timeline
get "/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline
get "/statuses/:id", MastodonAPIController, :get_status
get "/statuses/:id/context", MastodonAPIController, :get_context
get "/statuses/:id/favourited_by", MastodonAPIController, :favourited_by
get "/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by
get "/accounts/:id/statuses", MastodonAPIController, :user_statuses
get "/accounts/:id/followers", MastodonAPIController, :followers
get "/accounts/:id/following", MastodonAPIController, :following
get "/accounts/:id", MastodonAPIController, :user
get "/search", MastodonAPIController, :search
end
scope "/api", Pleroma.Web do
pipe_through :config
get "/help/test", TwitterAPI.UtilController, :help_test
post "/help/test", TwitterAPI.UtilController, :help_test
get "/statusnet/config", TwitterAPI.UtilController, :config
get "/statusnet/version", TwitterAPI.UtilController, :version
end
@instance Application.get_env(:pleroma, :instance)
@registrations_open Keyword.get(@instance, :registrations_open)
scope "/api", Pleroma.Web do
pipe_through :api
get "/statuses/public_timeline", TwitterAPI.Controller, :public_timeline
get "/statuses/public_and_external_timeline", TwitterAPI.Controller, :public_and_external_timeline
get "/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline
get "/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
get "/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline
get "/statuses/show/:id", TwitterAPI.Controller, :fetch_status
get "/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation
if @registrations_open do
post "/account/register", TwitterAPI.Controller, :register
end
get "/search", TwitterAPI.Controller, :search
get "/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline
end
scope "/api", Pleroma.Web do
pipe_through :authenticated_api
get "/account/verify_credentials", TwitterAPI.Controller, :verify_credentials
post "/account/verify_credentials", TwitterAPI.Controller, :verify_credentials
post "/account/update_profile", TwitterAPI.Controller, :update_profile
post "/account/update_profile_banner", TwitterAPI.Controller, :update_banner
post "/qvitter/update_background_image", TwitterAPI.Controller, :update_background
post "/account/most_recent_notification", TwitterAPI.Controller, :update_most_recent_notification
get "/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline
get "/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline
get "/statuses/mentions", TwitterAPI.Controller, :mentions_timeline
get "/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline
post "/statuses/update", TwitterAPI.Controller, :status_update
post "/statuses/retweet/:id", TwitterAPI.Controller, :retweet
post "/statuses/destroy/:id", TwitterAPI.Controller, :delete_post
post "/friendships/create", TwitterAPI.Controller, :follow
post "/friendships/destroy", TwitterAPI.Controller, :unfollow
post "/blocks/create", TwitterAPI.Controller, :block
post "/blocks/destroy", TwitterAPI.Controller, :unblock
post "/statusnet/media/upload", TwitterAPI.Controller, :upload
post "/media/upload", TwitterAPI.Controller, :upload_json
post "/favorites/create/:id", TwitterAPI.Controller, :favorite
post "/favorites/create", TwitterAPI.Controller, :favorite
post "/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite
post "/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar
get "/statuses/followers", TwitterAPI.Controller, :followers
get "/statuses/friends", TwitterAPI.Controller, :friends
get "/externalprofile/show", TwitterAPI.Controller, :external_profile
end
pipeline :ostatus do
plug :accepts, ["xml", "atom", "html"]
end
scope "/", Pleroma.Web do
pipe_through :ostatus
get "/objects/:uuid", OStatus.OStatusController, :object
get "/activities/:uuid", OStatus.OStatusController, :activity
get "/users/:nickname/feed", OStatus.OStatusController, :feed
get "/users/:nickname", OStatus.OStatusController, :feed_redirect
post "/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming
post "/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request
get "/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation
post "/push/subscriptions/:id", Websub.WebsubController, :websub_incoming
end
scope "/.well-known", Pleroma.Web do
pipe_through :well_known
get "/host-meta", WebFinger.WebFingerController, :host_meta
get "/webfinger", WebFinger.WebFingerController, :webfinger
end
scope "/", Fallback do
get "/*path", RedirectController, :redirector
end
end
defmodule Fallback.RedirectController do
use Pleroma.Web, :controller
def redirector(conn, _params) do
if Mix.env != :test do
conn
|> put_resp_content_type("text/html")
|> send_file(200, "priv/static/index.html")
end
end
end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 77fdb532f..eee1c9fa3 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -1,34 +1,95 @@
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
describe "create_notification" do
test "it doesn't create a notification for user if the user blocks the activity author" do
activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"])
user = insert(:user)
{:ok, user} = User.block(user, author)
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
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index cf60b4a51..f506d56a1 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1,473 +1,542 @@
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
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", "spoiler_text" => "2hu"})
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu"} = 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 == to_string(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 == to_string(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 "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)
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 to_string(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 to_string(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 to_string(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 == to_string(note_two.id)
end
end
describe "user relationships" do
test "returns the relationships for the current user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn = conn
|> assign(:user, user)
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
assert [relationship] = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
end
end
test "account fetching", %{conn: conn} do
user = insert(:user)
conn = conn
|> get("/api/v1/accounts/#{user.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(user.id)
conn = build_conn()
|> get("/api/v1/accounts/-1")
assert %{"error" => "Can't find user"} = json_response(conn, 404)
end
test "media upload", %{conn: conn} do
file = %Plug.Upload{content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg"}
user = insert(:user)
conn = conn
|> assign(:user, user)
|> post("/api/v1/media", %{"file" => file})
assert media = json_response(conn, 200)
assert media["type"] == "image"
end
test "hashtag timeline", %{conn: conn} do
following = insert(:user)
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
{:ok, [_activity]} = OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
conn = conn
|> get("/api/v1/timelines/tag/2hu")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(activity.id)
end
test "getting followers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn = conn
|> get("/api/v1/accounts/#{other_user.id}/followers")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(user.id)
end
test "getting following", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn = conn
|> get("/api/v1/accounts/#{user.id}/following")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "following / unfollowing a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn = conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/follow")
assert %{"id" => id, "following" => true} = json_response(conn, 200)
user = Repo.get(User, user.id)
conn = build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
assert %{"id" => id, "following" => false} = json_response(conn, 200)
user = Repo.get(User, user.id)
conn = build_conn()
|> assign(:user, user)
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "blocking / unblocking a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn = conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/block")
assert %{"id" => id, "blocking" => true} = json_response(conn, 200)
user = Repo.get(User, user.id)
conn = build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unblock")
assert %{"id" => id, "blocking" => false} = json_response(conn, 200)
end
test "getting a list of blocks", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.block(user, other_user)
conn = conn
|> assign(:user, user)
|> get("/api/v1/blocks")
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
end
test "unimplemented mute endpoints" do
user = insert(:user)
other_user = insert(:user)
["mute", "unmute"]
|> Enum.each(fn(endpoint) ->
conn = build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(other_user.id)
end)
end
test "unimplemented mutes, follow_requests, blocks, domain blocks" do
user = insert(:user)
["blocks", "domain_blocks", "mutes", "follow_requests"]
|> Enum.each(fn(endpoint) ->
conn = build_conn()
|> assign(:user, user)
|> get("/api/v1/#{endpoint}")
assert [] = json_response(conn, 200)
end)
end
test "account search", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
conn = conn
|> assign(:user, user)
|> get("/api/v1/accounts/search", %{"q" => "2hu"})
assert [account] = json_response(conn, 200)
assert account["id"] == to_string(user_three.id)
end
test "search", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
conn = conn
|> get("/api/v1/search", %{"q" => "2hu"})
assert results = json_response(conn, 200)
[account] = results["accounts"]
assert account["id"] == to_string(user_three.id)
assert results["hashtags"] == []
[status] = results["statuses"]
assert status["id"] == to_string(activity.id)
end
test "search fetches remote statuses", %{conn: conn} do
conn = conn
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
assert results = json_response(conn, 200)
[status] = results["statuses"]
assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
end
test "search fetches remote accounts", %{conn: conn} do
conn = conn
|> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
assert results = json_response(conn, 200)
[account] = results["accounts"]
assert account["acct"] == "shp@social.heldscal.la"
end
test "returns the favorites of a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
conn = conn
|> assign(:user, user)
|> get("/api/v1/favourites")
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

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 2, 9:04 AM (4 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1804081
Default Alt Text
(51 KB)

Event Timeline