Allow to group bookmarks in folders
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
f0468697cd
commit
d415686bb9
1
changelog.d/bookmark-folders.add
Normal file
1
changelog.d/bookmark-folders.add
Normal file
@ -0,0 +1 @@
|
|||||||
|
Allow to group bookmarks in folders
|
@ -40,6 +40,7 @@ Has these additional fields under the `pleroma` object:
|
|||||||
- `parent_visible`: If the parent of this post is visible to the user or not.
|
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||||
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
||||||
- `quotes_count`: the count of status quotes.
|
- `quotes_count`: the count of status quotes.
|
||||||
|
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
|
||||||
|
|
||||||
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
|
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
|
||||||
|
|
||||||
@ -65,6 +66,12 @@ Some apps operate under the assumption that no more than 4 attachments can be re
|
|||||||
|
|
||||||
Pleroma does not process remote images and therefore cannot include fields such as `meta` and `blurhash`. It does not support focal points or aspect ratios. The frontend is expected to handle it.
|
Pleroma does not process remote images and therefore cannot include fields such as `meta` and `blurhash`. It does not support focal points or aspect ratios. The frontend is expected to handle it.
|
||||||
|
|
||||||
|
## Bookmarks
|
||||||
|
|
||||||
|
The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
||||||
|
|
||||||
|
The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
||||||
|
|
||||||
## Accounts
|
## Accounts
|
||||||
|
|
||||||
The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc.
|
The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc.
|
||||||
|
@ -283,6 +283,52 @@ See [Admin-API](admin_api.md)
|
|||||||
* `id`: the id of the status
|
* `id`: the id of the status
|
||||||
* Response: JSON, returns a list of Mastodon Status entities
|
* Response: JSON, returns a list of Mastodon Status entities
|
||||||
|
|
||||||
|
## `GET /api/v1/pleroma/bookmark_folders`
|
||||||
|
### Gets user bookmark folders
|
||||||
|
* Authentication: required
|
||||||
|
|
||||||
|
* Response: JSON. Returns a list of bookmark folders.
|
||||||
|
* Example response:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "9umDrYheeY451cQnEe",
|
||||||
|
"name": "Read later",
|
||||||
|
"emoji": "🕓",
|
||||||
|
"source": {
|
||||||
|
"emoji": "🕓"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/bookmark_folders`
|
||||||
|
### Creates a bookmark folder
|
||||||
|
* Authentication: required
|
||||||
|
|
||||||
|
* Params:
|
||||||
|
* `name`: folder name
|
||||||
|
* `emoji`: folder emoji (optional)
|
||||||
|
* Response: JSON. Returns a single bookmark folder.
|
||||||
|
|
||||||
|
## `PATCH /api/v1/pleroma/bookmark_folders/:id`
|
||||||
|
### Updates a bookmark folder
|
||||||
|
* Authentication: required
|
||||||
|
|
||||||
|
* Params:
|
||||||
|
* `id`: folder id
|
||||||
|
* `name`: folder name (optional)
|
||||||
|
* `emoji`: folder emoji (optional)
|
||||||
|
* Response: JSON. Returns a single bookmark folder.
|
||||||
|
|
||||||
|
## `DELETE /api/v1/pleroma/bookmark_folders/:id`
|
||||||
|
### Deletes a bookmark folder
|
||||||
|
* Authentication: required
|
||||||
|
|
||||||
|
* Params:
|
||||||
|
* `id`: folder id
|
||||||
|
* Response: JSON. Returns a single bookmark folder.
|
||||||
|
|
||||||
## `/api/v1/pleroma/mascot`
|
## `/api/v1/pleroma/mascot`
|
||||||
### Gets user mascot image
|
### Gets user mascot image
|
||||||
* Method `GET`
|
* Method `GET`
|
||||||
|
@ -10,6 +10,7 @@ defmodule Pleroma.Bookmark do
|
|||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Bookmark
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@ -18,33 +19,46 @@ defmodule Pleroma.Bookmark do
|
|||||||
schema "bookmarks" do
|
schema "bookmarks" do
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
|
belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec create(Ecto.UUID.t(), Ecto.UUID.t()) ::
|
@spec create(Ecto.UUID.t(), Ecto.UUID.t()) ::
|
||||||
{:ok, Bookmark.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Bookmark.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def create(user_id, activity_id) do
|
def create(user_id, activity_id, folder_id \\ nil) do
|
||||||
attrs = %{
|
attrs = %{
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
activity_id: activity_id
|
activity_id: activity_id,
|
||||||
|
folder_id: folder_id
|
||||||
}
|
}
|
||||||
|
|
||||||
%Bookmark{}
|
%Bookmark{}
|
||||||
|> cast(attrs, [:user_id, :activity_id])
|
|> cast(attrs, [:user_id, :activity_id, :folder_id])
|
||||||
|> validate_required([:user_id, :activity_id])
|
|> validate_required([:user_id, :activity_id])
|
||||||
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
||||||
|> Repo.insert()
|
|> Repo.insert(
|
||||||
|
on_conflict: [set: [folder_id: folder_id]],
|
||||||
|
conflict_target: [:user_id, :activity_id]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec for_user_query(Ecto.UUID.t()) :: Ecto.Query.t()
|
@spec for_user_query(Ecto.UUID.t()) :: Ecto.Query.t()
|
||||||
def for_user_query(user_id) do
|
def for_user_query(user_id, folder_id \\ nil) do
|
||||||
Bookmark
|
Bookmark
|
||||||
|> where(user_id: ^user_id)
|
|> where(user_id: ^user_id)
|
||||||
|
|> maybe_filter_by_folder(folder_id)
|
||||||
|> join(:inner, [b], activity in assoc(b, :activity))
|
|> join(:inner, [b], activity in assoc(b, :activity))
|
||||||
|> preload([b, a], activity: a)
|
|> preload([b, a], activity: a)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_by_folder(query, nil), do: query
|
||||||
|
|
||||||
|
defp maybe_filter_by_folder(query, folder_id) do
|
||||||
|
query
|
||||||
|
|> where(folder_id: ^folder_id)
|
||||||
|
end
|
||||||
|
|
||||||
def get(user_id, activity_id) do
|
def get(user_id, activity_id) do
|
||||||
Bookmark
|
Bookmark
|
||||||
|> where(user_id: ^user_id)
|
|> where(user_id: ^user_id)
|
||||||
@ -62,4 +76,11 @@ defmodule Pleroma.Bookmark do
|
|||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|> Repo.delete()
|
|> Repo.delete()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_folder(bookmark, folder_id) do
|
||||||
|
bookmark
|
||||||
|
|> cast(%{folder_id: folder_id}, [:folder_id])
|
||||||
|
|> validate_required([:folder_id])
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
115
lib/pleroma/bookmark_folder.ex
Normal file
115
lib/pleroma/bookmark_folder.ex
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.BookmarkFolder do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
|
alias Pleroma.Emoji
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||||
|
|
||||||
|
schema "bookmark_folders" do
|
||||||
|
field(:name, :string)
|
||||||
|
field(:emoji, :string)
|
||||||
|
|
||||||
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_by_id(id), do: Repo.get_by(BookmarkFolder, id: id)
|
||||||
|
|
||||||
|
def create(user_id, name, emoji \\ nil) do
|
||||||
|
%BookmarkFolder{}
|
||||||
|
|> cast(
|
||||||
|
%{
|
||||||
|
user_id: user_id,
|
||||||
|
name: name,
|
||||||
|
emoji: emoji
|
||||||
|
},
|
||||||
|
[:user_id, :name, :emoji]
|
||||||
|
)
|
||||||
|
|> validate_required([:user_id, :name])
|
||||||
|
|> fix_emoji()
|
||||||
|
|> validate_emoji()
|
||||||
|
|> unique_constraint([:user_id, :name])
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(folder_id, name, emoji \\ nil) do
|
||||||
|
get_by_id(folder_id)
|
||||||
|
|> cast(
|
||||||
|
%{
|
||||||
|
name: name,
|
||||||
|
emoji: emoji
|
||||||
|
},
|
||||||
|
[:name, :emoji]
|
||||||
|
)
|
||||||
|
|> fix_emoji()
|
||||||
|
|> validate_emoji()
|
||||||
|
|> unique_constraint([:user_id, :name])
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_emoji(changeset) do
|
||||||
|
with {:emoji_field, emoji} when is_binary(emoji) <-
|
||||||
|
{:emoji_field, get_field(changeset, :emoji)},
|
||||||
|
{:fixed_emoji, emoji} <-
|
||||||
|
{:fixed_emoji,
|
||||||
|
emoji
|
||||||
|
|> Pleroma.Emoji.fully_qualify_emoji()
|
||||||
|
|> Pleroma.Emoji.maybe_quote()} do
|
||||||
|
put_change(changeset, :emoji, emoji)
|
||||||
|
else
|
||||||
|
{:emoji_field, _} -> changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_emoji(changeset) do
|
||||||
|
validate_change(changeset, :emoji, fn
|
||||||
|
:emoji, nil ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
:emoji, emoji ->
|
||||||
|
if Emoji.unicode?(emoji) or valid_local_custom_emoji?(emoji) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[emoji: "Invalid emoji"]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_local_custom_emoji?(emoji) do
|
||||||
|
with %{file: _path} <- Emoji.get(emoji) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(folder_id) do
|
||||||
|
BookmarkFolder
|
||||||
|
|> Repo.get_by(id: folder_id)
|
||||||
|
|> Repo.delete()
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user(user_id) do
|
||||||
|
BookmarkFolder
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def belongs_to_user?(folder_id, user_id) do
|
||||||
|
BookmarkFolder
|
||||||
|
|> where(id: ^folder_id, user_id: ^user_id)
|
||||||
|
|> Repo.exists?()
|
||||||
|
end
|
||||||
|
end
|
@ -137,7 +137,8 @@ defmodule Pleroma.Web.ApiSpec do
|
|||||||
"Scheduled statuses",
|
"Scheduled statuses",
|
||||||
"Search",
|
"Search",
|
||||||
"Status actions",
|
"Status actions",
|
||||||
"Media attachments"
|
"Media attachments",
|
||||||
|
"Bookmark folders"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.PleromaBookmarkFolderOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.BookmarkFolder
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
@spec open_api_operation(any()) :: any()
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Bookmark folders"],
|
||||||
|
summary: "All bookmark folders",
|
||||||
|
security: [%{"oAuth" => ["read:bookmarks"]}],
|
||||||
|
operationId: "PleromaAPI.BookmarkFolderController.index",
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Array of Bookmark Folders", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: BookmarkFolder
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Bookmark folders"],
|
||||||
|
summary: "Create a bookmark folder",
|
||||||
|
security: [%{"oAuth" => ["write:bookmarks"]}],
|
||||||
|
operationId: "PleromaAPI.BookmarkFolderController.create",
|
||||||
|
requestBody: request_body("Parameters", create_request(), required: true),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder),
|
||||||
|
422 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Bookmark folders"],
|
||||||
|
summary: "Update a bookmark folder",
|
||||||
|
security: [%{"oAuth" => ["write:bookmarks"]}],
|
||||||
|
operationId: "PleromaAPI.BookmarkFolderController.update",
|
||||||
|
parameters: [id_param()],
|
||||||
|
requestBody: request_body("Parameters", update_request(), required: true),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiError),
|
||||||
|
422 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Bookmark folders"],
|
||||||
|
summary: "Delete a bookmark folder",
|
||||||
|
security: [%{"oAuth" => ["write:bookmarks"]}],
|
||||||
|
operationId: "PleromaAPI.BookmarkFolderController.delete",
|
||||||
|
parameters: [id_param()],
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder),
|
||||||
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_request do
|
||||||
|
%Schema{
|
||||||
|
title: "BookmarkFolderCreateRequest",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "Folder name"
|
||||||
|
},
|
||||||
|
emoji: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: "Folder emoji"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_request do
|
||||||
|
%Schema{
|
||||||
|
title: "BookmarkFolderUpdateRequest",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
name: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: "Folder name"
|
||||||
|
},
|
||||||
|
emoji: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: "Folder emoji"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def id_param do
|
||||||
|
Operation.parameter(:id, :path, FlakeID.schema(), "Bookmark Folder ID",
|
||||||
|
example: "9umDrYheeY451cQnEe",
|
||||||
|
required: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
@ -256,6 +256,18 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||||||
description: "Privately bookmark a status",
|
description: "Privately bookmark a status",
|
||||||
operationId: "StatusController.bookmark",
|
operationId: "StatusController.bookmark",
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
|
requestBody:
|
||||||
|
request_body("Parameters", %Schema{
|
||||||
|
title: "StatusUpdateRequest",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
folder_id: %Schema{
|
||||||
|
nullable: true,
|
||||||
|
allOf: [FlakeID],
|
||||||
|
description: "ID of bookmarks folder, if any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response()
|
200 => status_response()
|
||||||
}
|
}
|
||||||
@ -430,7 +442,15 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do
|
|||||||
summary: "Bookmarked statuses",
|
summary: "Bookmarked statuses",
|
||||||
description: "Statuses the user has bookmarked",
|
description: "Statuses the user has bookmarked",
|
||||||
operationId: "StatusController.bookmarks",
|
operationId: "StatusController.bookmarks",
|
||||||
parameters: pagination_params(),
|
parameters: [
|
||||||
|
Operation.parameter(
|
||||||
|
:folder_id,
|
||||||
|
:query,
|
||||||
|
FlakeID.schema(),
|
||||||
|
"If provided, only display bookmarks from given folder"
|
||||||
|
)
|
||||||
|
| pagination_params()
|
||||||
|
],
|
||||||
security: [%{"oAuth" => ["read:bookmarks"]}],
|
security: [%{"oAuth" => ["read:bookmarks"]}],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
|
200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
|
||||||
|
26
lib/pleroma/web/api_spec/schemas/bookmark_folder.ex
Normal file
26
lib/pleroma/web/api_spec/schemas/bookmark_folder.ex
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
|
|
||||||
|
require OpenApiSpex
|
||||||
|
|
||||||
|
OpenApiSpex.schema(%{
|
||||||
|
title: "BookmarkFolder",
|
||||||
|
description: "Response schema for a bookmark folder",
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
id: FlakeID,
|
||||||
|
name: %Schema{type: :string, description: "Folder name"},
|
||||||
|
emoji: %Schema{type: :string, description: "Folder emoji", nullable: true}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"id" => "9toJCu5YZW7O7gfvH6",
|
||||||
|
"name" => "Read later",
|
||||||
|
"emoji" => nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Bookmark
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.ScheduledActivity
|
alias Pleroma.ScheduledActivity
|
||||||
@ -411,13 +412,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||||||
|
|
||||||
@doc "POST /api/v1/statuses/:id/bookmark"
|
@doc "POST /api/v1/statuses/:id/bookmark"
|
||||||
def bookmark(
|
def bookmark(
|
||||||
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn,
|
%{
|
||||||
|
assigns: %{user: user},
|
||||||
|
private: %{open_api_spex: %{body_params: body_params, params: %{id: id}}}
|
||||||
|
} = conn,
|
||||||
_
|
_
|
||||||
) do
|
) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
folder_id <- Map.get(body_params, :folder_id, nil),
|
||||||
|
folder_id <-
|
||||||
|
if(folder_id && BookmarkFolder.belongs_to_user?(folder_id, user.id),
|
||||||
|
do: folder_id,
|
||||||
|
else: nil
|
||||||
|
),
|
||||||
|
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -573,10 +583,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||||||
@doc "GET /api/v1/bookmarks"
|
@doc "GET /api/v1/bookmarks"
|
||||||
def bookmarks(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do
|
def bookmarks(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
folder_id = Map.get(params, :folder_id)
|
||||||
|
|
||||||
bookmarks =
|
bookmarks =
|
||||||
user.id
|
user.id
|
||||||
|> Bookmark.for_user_query()
|
|> Bookmark.for_user_query(folder_id)
|
||||||
|> Pleroma.Pagination.fetch_paginated(params)
|
|> Pleroma.Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
|
@ -184,7 +184,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||||||
|
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
||||||
|
|
||||||
bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
|
bookmark = Activity.get_bookmark(reblogged_parent_activity, opts[:for])
|
||||||
|
|
||||||
|
bookmark_folder =
|
||||||
|
if bookmark != nil do
|
||||||
|
bookmark.folder_id
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
mentions =
|
mentions =
|
||||||
activity.recipients
|
activity.recipients
|
||||||
@ -213,7 +220,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||||||
favourites_count: 0,
|
favourites_count: 0,
|
||||||
reblogged: reblogged?(reblogged_parent_activity, opts[:for]),
|
reblogged: reblogged?(reblogged_parent_activity, opts[:for]),
|
||||||
favourited: present?(favorited),
|
favourited: present?(favorited),
|
||||||
bookmarked: present?(bookmarked),
|
bookmarked: present?(bookmark),
|
||||||
muted: false,
|
muted: false,
|
||||||
pinned: pinned?,
|
pinned: pinned?,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
@ -227,7 +234,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||||||
emojis: [],
|
emojis: [],
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
local: activity.local,
|
local: activity.local,
|
||||||
pinned_at: pinned_at
|
pinned_at: pinned_at,
|
||||||
|
bookmark_folder: bookmark_folder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@ -264,7 +272,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||||||
|
|
||||||
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
||||||
|
|
||||||
bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
|
bookmark = Activity.get_bookmark(activity, opts[:for])
|
||||||
|
|
||||||
|
bookmark_folder =
|
||||||
|
if bookmark != nil do
|
||||||
|
bookmark.folder_id
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
client_posted_this_activity = opts[:for] && user.id == opts[:for].id
|
client_posted_this_activity = opts[:for] && user.id == opts[:for].id
|
||||||
|
|
||||||
@ -418,7 +433,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||||||
favourites_count: like_count,
|
favourites_count: like_count,
|
||||||
reblogged: reblogged?(activity, opts[:for]),
|
reblogged: reblogged?(activity, opts[:for]),
|
||||||
favourited: present?(favorited),
|
favourited: present?(favorited),
|
||||||
bookmarked: present?(bookmarked),
|
bookmarked: present?(bookmark),
|
||||||
muted: muted,
|
muted: muted,
|
||||||
pinned: pinned?,
|
pinned: pinned?,
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
@ -448,7 +463,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||||||
emoji_reactions: emoji_reactions,
|
emoji_reactions: emoji_reactions,
|
||||||
parent_visible: visible_for_user?(reply_to, opts[:for]),
|
parent_visible: visible_for_user?(reply_to, opts[:for]),
|
||||||
pinned_at: pinned_at,
|
pinned_at: pinned_at,
|
||||||
quotes_count: object.data["quotesCount"] || 0
|
quotes_count: object.data["quotesCount"] || 0,
|
||||||
|
bookmark_folder: bookmark_folder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.BookmarkFolderController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
|
# Note: scope not present in Mastodon: read:bookmarks
|
||||||
|
plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index)
|
||||||
|
|
||||||
|
# Note: scope not present in Mastodon: write:bookmarks
|
||||||
|
plug(
|
||||||
|
OAuthScopesPlug,
|
||||||
|
%{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete]
|
||||||
|
)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBookmarkFolderOperation
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|
with folders <- BookmarkFolder.for_user(user.id) do
|
||||||
|
conn
|
||||||
|
|> render("index.json", %{folders: folders, as: :folder})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(
|
||||||
|
%{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
with {:ok, folder} <- BookmarkFolder.create(user.id, params[:name], params[:emoji]) do
|
||||||
|
render(conn, "show.json", folder: folder)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(
|
||||||
|
%{
|
||||||
|
assigns: %{user: user},
|
||||||
|
private: %{open_api_spex: %{body_params: params, params: %{id: id}}}
|
||||||
|
} = conn,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
with true <- BookmarkFolder.belongs_to_user?(id, user.id),
|
||||||
|
{:ok, folder} <- BookmarkFolder.update(id, params[:name], params[:emoji]) do
|
||||||
|
render(conn, "show.json", folder: folder)
|
||||||
|
else
|
||||||
|
false -> {:error, :forbidden}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(
|
||||||
|
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
with true <- BookmarkFolder.belongs_to_user?(id, user.id),
|
||||||
|
{:ok, folder} <- BookmarkFolder.delete(id) do
|
||||||
|
render(conn, "show.json", folder: folder)
|
||||||
|
else
|
||||||
|
false -> {:error, :forbidden}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
44
lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex
Normal file
44
lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
|
alias Pleroma.Emoji
|
||||||
|
alias Pleroma.Web.Endpoint
|
||||||
|
|
||||||
|
def render("show.json", %{folder: %BookmarkFolder{} = folder}) do
|
||||||
|
%{
|
||||||
|
id: folder.id |> to_string(),
|
||||||
|
name: folder.name,
|
||||||
|
emoji: get_emoji(folder.emoji),
|
||||||
|
source: %{
|
||||||
|
emoji: folder.emoji
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("index.json", %{folders: folders} = opts) do
|
||||||
|
render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_emoji(nil) do
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_emoji(emoji) do
|
||||||
|
if Emoji.unicode?(emoji) do
|
||||||
|
emoji
|
||||||
|
else
|
||||||
|
emoji = Emoji.get(emoji)
|
||||||
|
|
||||||
|
if emoji != nil do
|
||||||
|
Endpoint.url() |> URI.merge(emoji.relative_url) |> to_string()
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -580,6 +580,11 @@ defmodule Pleroma.Web.Router do
|
|||||||
|
|
||||||
get("/backups", BackupController, :index)
|
get("/backups", BackupController, :index)
|
||||||
post("/backups", BackupController, :create)
|
post("/backups", BackupController, :create)
|
||||||
|
|
||||||
|
get("/bookmark_folders", BookmarkFolderController, :index)
|
||||||
|
post("/bookmark_folders", BookmarkFolderController, :create)
|
||||||
|
patch("/bookmark_folders/:id", BookmarkFolderController, :update)
|
||||||
|
delete("/bookmark_folders/:id", BookmarkFolderController, :delete)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateBookmarkFolders do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:bookmark_folders, primary_key: false) do
|
||||||
|
add(:id, :uuid, primary_key: true)
|
||||||
|
add(:name, :string, null: false)
|
||||||
|
add(:emoji, :string)
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
alter table(:bookmarks) do
|
||||||
|
add_if_not_exists(
|
||||||
|
:folder_id,
|
||||||
|
references(:bookmark_folders, type: :uuid, on_delete: :nilify_all)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:bookmark_folders, [:user_id, :name]))
|
||||||
|
end
|
||||||
|
end
|
60
test/pleroma/bookmark_folder_test.exs
Normal file
60
test/pleroma/bookmark_folder_test.exs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.BookmarkFolderTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
|
|
||||||
|
describe "create/3" do
|
||||||
|
test "with valid params" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, folder} = BookmarkFolder.create(user.id, "Read later", "🕓")
|
||||||
|
assert folder.user_id == user.id
|
||||||
|
assert folder.name == "Read later"
|
||||||
|
assert folder.emoji == "🕓"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with invalid params" do
|
||||||
|
{:error, changeset} = BookmarkFolder.create(nil, "", "not an emoji")
|
||||||
|
refute changeset.valid?
|
||||||
|
|
||||||
|
assert changeset.errors == [
|
||||||
|
emoji: {"Invalid emoji", []},
|
||||||
|
user_id: {"can't be blank", [validation: :required]},
|
||||||
|
name: {"can't be blank", [validation: :required]}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update/3" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, folder} = BookmarkFolder.create(user.id, "Read ltaer")
|
||||||
|
{:ok, folder} = BookmarkFolder.update(folder.id, "Read later")
|
||||||
|
assert folder.name == "Read later"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "for_user/1" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = BookmarkFolder.create(user.id, "Folder 1")
|
||||||
|
{:ok, _} = BookmarkFolder.create(user.id, "Folder 2")
|
||||||
|
{:ok, _} = BookmarkFolder.create(other_user.id, "Folder 3")
|
||||||
|
|
||||||
|
folders = BookmarkFolder.for_user(user.id)
|
||||||
|
|
||||||
|
assert length(folders) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "belongs_to_user?/2" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, folder} = BookmarkFolder.create(user.id, "Folder")
|
||||||
|
|
||||||
|
assert true == BookmarkFolder.belongs_to_user?(folder.id, user.id)
|
||||||
|
assert false == BookmarkFolder.belongs_to_user?(folder.id, other_user.id)
|
||||||
|
end
|
||||||
|
end
|
@ -6,15 +6,17 @@ defmodule Pleroma.BookmarkTest do
|
|||||||
use Pleroma.DataCase, async: true
|
use Pleroma.DataCase, async: true
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
alias Pleroma.Bookmark
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
describe "create/2" do
|
describe "create/3" do
|
||||||
test "with valid params" do
|
test "with valid params" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"})
|
||||||
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
|
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
|
||||||
assert bookmark.user_id == user.id
|
assert bookmark.user_id == user.id
|
||||||
assert bookmark.activity_id == activity.id
|
assert bookmark.activity_id == activity.id
|
||||||
|
assert bookmark.folder_id == nil
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with invalid params" do
|
test "with invalid params" do
|
||||||
@ -26,6 +28,19 @@ defmodule Pleroma.BookmarkTest do
|
|||||||
activity_id: {"can't be blank", [validation: :required]}
|
activity_id: {"can't be blank", [validation: :required]}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "update existing bookmark folder" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"})
|
||||||
|
|
||||||
|
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
|
||||||
|
assert bookmark.folder_id == nil
|
||||||
|
|
||||||
|
{:ok, bookmark_folder} = BookmarkFolder.create(user.id, "Read later")
|
||||||
|
|
||||||
|
{:ok, bookmark} = Bookmark.create(user.id, activity.id, bookmark_folder.id)
|
||||||
|
assert bookmark.folder_id == bookmark_folder.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "destroy/2" do
|
describe "destroy/2" do
|
||||||
|
@ -1828,6 +1828,60 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|||||||
json_response_and_validate_schema(bookmarks, 200)
|
json_response_and_validate_schema(bookmarks, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "bookmark folders" do
|
||||||
|
%{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"])
|
||||||
|
|
||||||
|
{:ok, folder} = Pleroma.BookmarkFolder.create(user.id, "folder")
|
||||||
|
author = insert(:user)
|
||||||
|
|
||||||
|
folder_bookmarks_uri = "/api/v1/bookmarks?folder_id=#{folder.id}"
|
||||||
|
|
||||||
|
{:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
|
||||||
|
{:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
|
||||||
|
|
||||||
|
# Add bookmark with a folder
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity1.id}/bookmark", %{folder_id: folder.id})
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] ==
|
||||||
|
folder.id
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity2.id}/bookmark")
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
|
||||||
|
assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] == nil
|
||||||
|
|
||||||
|
bookmarks =
|
||||||
|
get(conn, folder_bookmarks_uri)
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(bookmarks) == 1
|
||||||
|
|
||||||
|
# Update folder for existing bookmark
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses/#{activity2.id}/bookmark", %{folder_id: folder.id})
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
|
||||||
|
|
||||||
|
assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] ==
|
||||||
|
folder.id
|
||||||
|
|
||||||
|
bookmarks =
|
||||||
|
get(conn, folder_bookmarks_uri)
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(bookmarks) == 2
|
||||||
|
end
|
||||||
|
|
||||||
describe "conversation muting" do
|
describe "conversation muting" do
|
||||||
setup do: oauth_access(["write:mutes"])
|
setup do: oauth_access(["write:mutes"])
|
||||||
|
|
||||||
|
@ -341,7 +341,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||||||
emoji_reactions: [],
|
emoji_reactions: [],
|
||||||
parent_visible: false,
|
parent_visible: false,
|
||||||
pinned_at: nil,
|
pinned_at: nil,
|
||||||
quotes_count: 0
|
quotes_count: 0,
|
||||||
|
bookmark_folder: nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.BookmarkFolderControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.BookmarkFolder
|
||||||
|
# alias Pleroma.Object
|
||||||
|
# alias Pleroma.Tests.Helpers
|
||||||
|
# alias Pleroma.UnstubbedConfigMock, as: ConfigMock
|
||||||
|
# alias Pleroma.User
|
||||||
|
# alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
# alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
# import Mox
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "GET /api/v1/pleroma/bookmark_folders" do
|
||||||
|
setup do: oauth_access(["read:bookmarks"])
|
||||||
|
|
||||||
|
test "it lists bookmark folders", %{conn: conn, user: user} do
|
||||||
|
{:ok, folder} = BookmarkFolder.create(user.id, "Bookmark folder")
|
||||||
|
|
||||||
|
folder_id = folder.id
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/bookmark_folders")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"id" => ^folder_id,
|
||||||
|
"name" => "Bookmark folder",
|
||||||
|
"emoji" => nil,
|
||||||
|
"source" => %{
|
||||||
|
"emoji" => nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] = result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "POST /api/v1/pleroma/bookmark_folders" do
|
||||||
|
setup do: oauth_access(["write:bookmarks"])
|
||||||
|
|
||||||
|
test "it creates a bookmark folder", %{conn: conn} do
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/pleroma/bookmark_folders", %{
|
||||||
|
name: "Bookmark folder",
|
||||||
|
emoji: "📁"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"name" => "Bookmark folder",
|
||||||
|
"emoji" => "📁",
|
||||||
|
"source" => %{
|
||||||
|
"emoji" => "📁"
|
||||||
|
}
|
||||||
|
} = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns error for invalid emoji", %{conn: conn} do
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/pleroma/bookmark_folders", %{
|
||||||
|
name: "Bookmark folder",
|
||||||
|
emoji: "not an emoji"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(422)
|
||||||
|
|
||||||
|
assert %{"error" => "Invalid emoji"} = result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "PATCH /api/v1/pleroma/bookmark_folders/:id" do
|
||||||
|
setup do: oauth_access(["write:bookmarks"])
|
||||||
|
|
||||||
|
test "it updates a bookmark folder", %{conn: conn, user: user} do
|
||||||
|
{:ok, folder} = BookmarkFolder.create(user.id, "Bookmark folder")
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> patch("/api/v1/pleroma/bookmark_folders/#{folder.id}", %{
|
||||||
|
name: "bookmark folder"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"name" => "bookmark folder"
|
||||||
|
} = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns error when updating others' folders", %{conn: conn} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, folder} = BookmarkFolder.create(other_user.id, "Bookmark folder")
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> patch("/api/v1/pleroma/bookmark_folders/#{folder.id}", %{
|
||||||
|
name: "bookmark folder"
|
||||||
|
})
|
||||||
|
|> json_response_and_validate_schema(403)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"error" => "Access denied"
|
||||||
|
} = result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "DELETE /api/v1/pleroma/bookmark_folders/:id" do
|
||||||
|
setup do: oauth_access(["write:bookmarks"])
|
||||||
|
|
||||||
|
test "it deleting a bookmark folder", %{conn: conn, user: user} do
|
||||||
|
{:ok, folder} = BookmarkFolder.create(user.id, "Bookmark folder")
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> delete("/api/v1/pleroma/bookmark_folders/#{folder.id}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
folders = BookmarkFolder.for_user(user.id)
|
||||||
|
|
||||||
|
assert length(folders) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns error when deleting others' folders", %{conn: conn} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, folder} = BookmarkFolder.create(other_user.id, "Bookmark folder")
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/pleroma/bookmark_folders/#{folder.id}")
|
||||||
|
|> json_response_and_validate_schema(403)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"error" => "Access denied"
|
||||||
|
} = result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
3
uploads/.gitignore
vendored
3
uploads/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
# Git will ignore everything in this directory except this file.
|
|
||||||
*
|
|
||||||
!.gitignore
|
|
Loading…
Reference in New Issue
Block a user