grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
result = %{
ancestors:
- StatusView.render(
- "index.json",
+ StatusView.render("index.json",
for: user,
activities: grouped_activities[true] || [],
as: :activity
|> Enum.reverse(),
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
descendants:
- StatusView.render(
- "index.json",
+ StatusView.render("index.json",
for: user,
activities: grouped_activities[false] || [],
as: :activity
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)
+ targets = User.get_all_by_ids(List.wrap(id))
conn
|> put_view(AccountView)
# Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
- def update_media(%{assigns: %{user: user}} = conn, data) do
- with %Object{} = object <- Repo.get(Object, data["id"]),
+ def update_media(
+ %{assigns: %{user: user}} = conn,
+ %{"id" => id, "description" => description} = _
+ )
+ when is_binary(description) do
+ with %Object{} = object <- Repo.get(Object, id),
true <- Object.authorize_mutation(object, user),
- true <- is_binary(data["description"]),
- description <- data["description"] do
- new_data = %{object.data | "name" => description}
-
- {:ok, _} =
- object
- |> Object.change(%{data: new_data})
- |> Repo.update()
-
- attachment_data = Map.put(new_data, "id", object.id)
+ {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
+ attachment_data = Map.put(data, "id", object.id)
conn
|> put_view(StatusView)
end
end
+ def update_media(_conn, _data), do: {:error, :bad_request}
+
def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(
def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
%{} = attachment_data <- Map.put(object.data, "id", object.id),
- %{type: type} = rendered <-
- StatusView.render("attachment.json", %{attachment: attachment_data}) do
- # Reject if not an image
- if type == "image" do
- # Sure!
- # Save to the user's info
- info_changeset = User.Info.mascot_update(user.info, rendered)
-
- user_changeset =
- user
- |> Changeset.change()
- |> Changeset.put_embed(:info, info_changeset)
-
- {:ok, _user} = User.update_and_set_cache(user_changeset)
-
- conn
- |> json(rendered)
- else
+ %{type: "image"} = rendered <-
+ StatusView.render("attachment.json", %{attachment: attachment_data}),
+ {:ok, _user} = User.update_mascot(user, rendered) do
+ json(conn, rendered)
+ else
+ %{type: _type} = _ ->
render_error(conn, :unsupported_media_type, "mascots can only be images")
- end
+
+ e ->
+ e
end
end
def get_mascot(%{assigns: %{user: user}} = conn, _params) do
mascot = User.get_mascot(user)
- conn
- |> json(mascot)
+ json(conn, mascot)
end
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|> put_view(AccountView)
|> render("relationship.json", %{user: user, target: subscription_target})
else
- {:error, message} ->
- conn
- |> put_status(:forbidden)
- |> json(%{error: message})
+ nil -> {:error, :not_found}
+ e -> e
end
end
|> put_view(AccountView)
|> render("relationship.json", %{user: user, target: subscription_target})
else
- {:error, message} ->
- conn
- |> put_status(:forbidden)
- |> json(%{error: message})
+ nil -> {:error, :not_found}
+ e -> e
end
end
def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
lists = Pleroma.List.get_lists_account_belongs(user, account_id)
- res = ListView.render("lists.json", lists: lists)
- json(conn, res)
+
+ conn
+ |> put_view(ListView)
+ |> render("index.json", %{lists: lists})
end
def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
@doc "Local Mastodon FE login init action"
def login(conn, %{"code" => auth_token}) do
with {:ok, app} <- get_or_make_app(),
- %Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id),
+ {:ok, auth} <- Authorization.get_by_token(app, auth_token),
{:ok, token} <- Token.exchange_token(app, auth) do
conn
|> put_session(:oauth_token, token.token)
def login(conn, _) do
with {:ok, app} <- get_or_make_app() do
path =
- o_auth_path(
- conn,
- :authorize,
+ o_auth_path(conn, :authorize,
response_type: "code",
client_id: app.client_id,
redirect_uri: ".",
end
end
+ @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
defp get_or_make_app do
- find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
- scopes = ["read", "write", "follow", "push"]
-
- with %App{} = app <- Repo.get_by(App, find_attrs) do
- {:ok, app} =
- if app.scopes == scopes do
- {:ok, app}
- else
- app
- |> Changeset.change(%{scopes: scopes})
- |> Repo.update()
- end
-
- {:ok, app}
- else
- _e ->
- cs =
- App.register_changeset(
- %App{},
- Map.put(find_attrs, :scopes, scopes)
- )
-
- Repo.insert(cs)
- end
+ App.get_or_make(
+ %{client_name: @local_mastodon_name, redirect_uris: "."},
+ ["read", "write", "follow", "push"]
+ )
end
def logout(conn, _) do
|> redirect(to: "/")
end
- def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- Logger.debug("Unimplemented, returning unmodified relationship")
-
- with %User{} = target <- User.get_cached_by_id(id) do
- conn
- |> put_view(AccountView)
- |> render("relationship.json", %{user: user, target: target})
- end
- end
-
+ # Stubs for unimplemented mastodon api
+ #
def empty_array(conn, _) do
Logger.debug("Unimplemented, returning an empty array")
json(conn, [])
end
- def empty_object(conn, _) do
- Logger.debug("Unimplemented, returning an empty object")
- json(conn, %{})
- end
-
def get_filters(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)
res = FilterView.render("filters.json", filters: filters)
json(conn, data)
else
_e ->
- %{}
+ json(conn, %{})
end
end
end
end
- def account_register(%{assigns: %{app: _app}} = conn, _params) do
+ def account_register(%{assigns: %{app: _app}} = conn, _) do
render_error(conn, :bad_request, "Missing parameters")
end
end
end
- def try_render(conn, target, params)
- when is_binary(target) do
+ defp try_render(conn, target, params)
+ when is_binary(target) do
case render(conn, target, params) do
nil -> render_error(conn, :not_implemented, "Can't display this activity")
res -> res
end
end
- def try_render(conn, _, _) do
+ defp try_render(conn, _, _) do
render_error(conn, :not_implemented, "Can't display this activity")
end
assert to_string(other_user.id) == relationship["id"]
end
+
+ test "returns an empty list when bad request", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/relationships", %{})
+
+ assert [] = json_response(conn, 200)
+ end
end
describe "media upload" do
end
end
- test "mascot upload", %{conn: conn} do
- user = insert(:user)
+ describe "/api/v1/pleroma/mascot" do
+ test "mascot upload", %{conn: conn} do
+ user = insert(:user)
- non_image_file = %Plug.Upload{
- content_type: "audio/mpeg",
- path: Path.absname("test/fixtures/sound.mp3"),
- filename: "sound.mp3"
- }
+ non_image_file = %Plug.Upload{
+ content_type: "audio/mpeg",
+ path: Path.absname("test/fixtures/sound.mp3"),
+ filename: "sound.mp3"
+ }
- conn =
- conn
- |> assign(:user, user)
- |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
+ conn =
+ conn
+ |> assign(:user, user)
+ |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
- assert json_response(conn, 415)
+ assert json_response(conn, 415)
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
- conn =
- build_conn()
- |> assign(:user, user)
- |> put("/api/v1/pleroma/mascot", %{"file" => file})
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> put("/api/v1/pleroma/mascot", %{"file" => file})
- assert %{"id" => _, "type" => image} = json_response(conn, 200)
- end
+ assert %{"id" => _, "type" => image} = json_response(conn, 200)
+ end
- test "mascot retrieving", %{conn: conn} do
- user = insert(:user)
- # When user hasn't set a mascot, we should just get pleroma tan back
- conn =
- conn
- |> assign(:user, user)
- |> get("/api/v1/pleroma/mascot")
+ test "mascot retrieving", %{conn: conn} do
+ user = insert(:user)
+ # When user hasn't set a mascot, we should just get pleroma tan back
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/pleroma/mascot")
- assert %{"url" => url} = json_response(conn, 200)
- assert url =~ "pleroma-fox-tan-smol"
+ assert %{"url" => url} = json_response(conn, 200)
+ assert url =~ "pleroma-fox-tan-smol"
- # When a user sets their mascot, we should get that back
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
+ # When a user sets their mascot, we should get that back
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
- conn =
- build_conn()
- |> assign(:user, user)
- |> put("/api/v1/pleroma/mascot", %{"file" => file})
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> put("/api/v1/pleroma/mascot", %{"file" => file})
- assert json_response(conn, 200)
+ assert json_response(conn, 200)
- user = User.get_cached_by_id(user.id)
+ user = User.get_cached_by_id(user.id)
- conn =
- build_conn()
- |> assign(:user, user)
- |> get("/api/v1/pleroma/mascot")
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> get("/api/v1/pleroma/mascot")
- assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
- assert url =~ "an_image"
+ assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
+ assert url =~ "an_image"
+ end
end
test "hashtag timeline", %{conn: conn} do
end
end
- test "subscribing / unsubscribing to a user", %{conn: conn} do
- user = insert(:user)
- subscription_target = insert(:user)
+ describe "subscribing / unsubscribing" do
+ test "subscribing / unsubscribing to a user", %{conn: conn} do
+ user = insert(:user)
+ subscription_target = insert(:user)
- conn =
- conn
- |> assign(:user, user)
- |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
- assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
+ assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
- conn =
- build_conn()
- |> assign(:user, user)
- |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
+
+ assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
+ end
+ end
+
+ describe "subscribing" do
+ test "returns 404 when subscription_target not found", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/target_id/subscribe")
+
+ assert %{"error" => "Record not found"} = json_response(conn, 404)
+ end
+ end
+
+ describe "unsubscribing" do
+ test "returns 404 when subscription_target not found", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> post("/api/v1/pleroma/accounts/target_id/unsubscribe")
- assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
+ assert %{"error" => "Record not found"} = json_response(conn, 404)
+ end
end
test "getting a list of mutes", %{conn: conn} do
}
}
end
+
+ test "returns empty object when id invalid", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/v1/statuses/9eoozpwTul5mjSEDRI/card")
+ |> json_response(200)
+
+ assert response == %{}
+ end
end
test "bookmarks" do
assert conn.status == 302
assert redirected_to(conn) == path
end
+ end
+
+ describe "GET /web/login" do
+ test "redirects to /oauth/authorize", %{conn: conn} do
+ app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
+ conn = get(conn, "/web/login", %{})
+
+ assert conn.status == 302
+
+ assert redirected_to(conn) ==
+ "/oauth/authorize?response_type=code&client_id=#{app.client_id}&redirect_uri=.&scope=read+write+follow+push"
+ end
test "redirects to the getting-started page when referer is not present", %{conn: conn} do
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
assert conn.status == 302
assert redirected_to(conn) == "/web/getting-started"
end
+
+ test "redirects to the getting-started page when user assigned", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> get("/web/login", %{})
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/web/getting-started"
+ end
end
describe "scheduled activities" do
end
describe "create account by app" do
+ setup do
+ valid_params = %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true
+ }
+
+ [valid_params: valid_params]
+ end
+
test "Account registration via Application", %{conn: conn} do
conn =
conn
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
+ bio: "Test Bio",
agreement: true
})
assert token_from_db.user.info.confirmation_pending
end
+ test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
+ _user = insert(:user, email: "lain@example.org")
+ app_token = insert(:oauth_token, user: nil)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+
+ res = post(conn, "/api/v1/accounts", valid_params)
+ assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
+ end
+
test "rate limit", %{conn: conn} do
app_token = insert(:oauth_token, user: nil)
assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
end
+
+ test "returns bad_request if missing required params", %{
+ conn: conn,
+ valid_params: valid_params
+ } do
+ app_token = insert(:oauth_token, user: nil)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+
+ res = post(conn, "/api/v1/accounts", valid_params)
+ assert json_response(res, 200)
+
+ Enum.each(valid_params, fn {attr, _} ->
+ res =
+ conn
+ |> Map.put(
+ :remote_ip,
+ {:rand.uniform(15), :rand.uniform(15), :rand.uniform(15), :rand.uniform(15)}
+ )
+ |> post("/api/v1/accounts", Map.delete(valid_params, attr))
+
+ assert json_response(res, 400) == %{"error" => "Missing parameters"}
+ end)
+ end
+
+ test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> "invalid-token")
+
+ res = post(conn, "/api/v1/accounts", valid_params)
+ assert json_response(res, 403) == %{"error" => "Invalid credentials"}
+ end
end
describe "GET /api/v1/polls/:id" do
]
end
end
+
+ describe "PUT /api/v1/media/:id" do
+ setup do
+ actor = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, %Object{} = object} =
+ ActivityPub.upload(
+ file,
+ actor: User.ap_id(actor),
+ description: "test-m"
+ )
+
+ [actor: actor, object: object]
+ end
+
+ test "updates name of media", %{conn: conn, actor: actor, object: object} do
+ media =
+ conn
+ |> assign(:user, actor)
+ |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"})
+ |> json_response(:ok)
+
+ assert media["description"] == "test-media"
+ assert refresh_record(object).data["name"] == "test-media"
+ end
+
+ test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do
+ media =
+ conn
+ |> assign(:user, actor)
+ |> put("/api/v1/media/#{object.id}", %{})
+ |> json_response(400)
+
+ assert media == %{"error" => "bad_request"}
+ end
+ end
+
+ describe "DELETE /auth/sign_out" do
+ test "redirect to root page", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> delete("/auth/sign_out")
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/"
+ end
+ end
+
+ describe "GET /api/v1/accounts/:id/lists - account_lists" do
+ test "returns lists to which the account belongs", %{conn: conn} do
+ user = insert(:user)
+ other_user = insert(:user)
+ assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user)
+ {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
+
+ res =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/#{other_user.id}/lists")
+ |> json_response(200)
+
+ assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
+ end
+ end
+
+ describe "empty_array, stubs for mastodon api" do
+ test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do
+ user = insert(:user)
+
+ res =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/accounts/#{user.id}/identity_proofs")
+ |> json_response(200)
+
+ assert res == []
+ end
+
+ test "GET /api/v1/endorsements", %{conn: conn} do
+ user = insert(:user)
+
+ res =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/endorsements")
+ |> json_response(200)
+
+ assert res == []
+ end
+
+ test "GET /api/v1/trends", %{conn: conn} do
+ user = insert(:user)
+
+ res =
+ conn
+ |> assign(:user, user)
+ |> get("/api/v1/trends")
+ |> json_response(200)
+
+ assert res == []
+ end
+ end
end