Merge remote-tracking branch 'origin/logger-metadata' into spc2
This commit is contained in:
commit
b6344879d6
|
@ -26,10 +26,10 @@ cache: &global_cache_policy
|
||||||
- _build
|
- _build
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- check-changelog
|
|
||||||
- build
|
- build
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
|
- check-changelog
|
||||||
- benchmark
|
- benchmark
|
||||||
- deploy
|
- deploy
|
||||||
- release
|
- release
|
||||||
|
@ -113,7 +113,7 @@ benchmark:
|
||||||
variables:
|
variables:
|
||||||
MIX_ENV: benchmark
|
MIX_ENV: benchmark
|
||||||
services:
|
services:
|
||||||
- name: postgres:9.6-alpine
|
- name: postgres:11.22-alpine
|
||||||
alias: postgres
|
alias: postgres
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
script:
|
script:
|
||||||
|
@ -169,25 +169,6 @@ unit-testing-1.12-erratic:
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix test --only=erratic
|
- mix test --only=erratic
|
||||||
|
|
||||||
unit-testing-1.12-rum:
|
|
||||||
extends:
|
|
||||||
- .build_changes_policy
|
|
||||||
- .using-ci-base
|
|
||||||
stage: test
|
|
||||||
cache: *testing_cache_policy
|
|
||||||
services:
|
|
||||||
- name: git.pleroma.social:5050/pleroma/pleroma/postgres-with-rum-13
|
|
||||||
alias: postgres
|
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
|
||||||
variables:
|
|
||||||
<<: *global_variables
|
|
||||||
RUM_ENABLED: "true"
|
|
||||||
script:
|
|
||||||
- mix ecto.create
|
|
||||||
- mix ecto.migrate
|
|
||||||
- "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
|
||||||
- mix test --preload-modules
|
|
||||||
|
|
||||||
formatting-1.13:
|
formatting-1.13:
|
||||||
extends: .build_changes_policy
|
extends: .build_changes_policy
|
||||||
image: &formatting_elixir elixir:1.13-alpine
|
image: &formatting_elixir elixir:1.13-alpine
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Include following/followers in backups
|
|
@ -0,0 +1 @@
|
||||||
|
Allow to group bookmarks in folders
|
|
@ -0,0 +1 @@
|
||||||
|
Add ForceMention MRF
|
|
@ -0,0 +1 @@
|
||||||
|
Video framegrabs were not working correctly after the change to use Exile to execute ffmpeg
|
|
@ -0,0 +1 @@
|
||||||
|
Add contact account to InstanceView
|
|
@ -0,0 +1 @@
|
||||||
|
Handle cases when users.inbox is nil.
|
|
@ -0,0 +1 @@
|
||||||
|
Verify profile link ownership with rel="me"
|
|
@ -0,0 +1 @@
|
||||||
|
Notifications: improve performance by filtering on users table instead of activities table
|
|
@ -0,0 +1 @@
|
||||||
|
Disable jit by default for PostgreSQL
|
|
@ -0,0 +1 @@
|
||||||
|
Expose nonAnonymous field from Smithereen polls
|
|
@ -0,0 +1 @@
|
||||||
|
Set default values on validators for transient objects (attachment, poll options)
|
|
@ -566,6 +566,20 @@
|
||||||
"Cool instance"
|
"Cool instance"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :status_page,
|
||||||
|
type: :string,
|
||||||
|
description: "A page where people can see the status of the server during an outage",
|
||||||
|
suggestions: [
|
||||||
|
"https://status.pleroma.example.org"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :contact_username,
|
||||||
|
type: :string,
|
||||||
|
description: "Instance owner username",
|
||||||
|
suggestions: ["admin"]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :limit,
|
key: :limit,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
hostname: System.get_env("DB_HOST") || "localhost",
|
hostname: System.get_env("DB_HOST") || "localhost",
|
||||||
port: System.get_env("DB_PORT") || "5432",
|
port: System.get_env("DB_PORT") || "5432",
|
||||||
pool: Ecto.Adapters.SQL.Sandbox,
|
pool: Ecto.Adapters.SQL.Sandbox,
|
||||||
pool_size: 50
|
pool_size: System.schedulers_online() * 2
|
||||||
|
|
||||||
config :pleroma, :dangerzone, override_repo_pool_size: true
|
config :pleroma, :dangerzone, override_repo_pool_size: true
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,8 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
||||||
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
|
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
|
||||||
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline.
|
* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline.
|
||||||
* `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)
|
* `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.ForceMention`: Forces posts to include a mention of the author of parent post or the author of quoted post.
|
||||||
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||||
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||||
|
|
||||||
|
@ -272,6 +273,10 @@ Notes:
|
||||||
#### :mrf_inline_quote
|
#### :mrf_inline_quote
|
||||||
* `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `<bdi>RT:</bdi> {url}`
|
* `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `<bdi>RT:</bdi> {url}`
|
||||||
|
|
||||||
|
#### :mrf_force_mention
|
||||||
|
* `mention_parent`: Whether to append mention of parent post author
|
||||||
|
* `mention_quoted`: Whether to append mention of parent quoted author
|
||||||
|
|
||||||
### :activitypub
|
### :activitypub
|
||||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||||
* `outgoing_blocks`: Whether to federate blocks to other instances
|
* `outgoing_blocks`: Whether to federate blocks to other instances
|
||||||
|
|
|
@ -40,6 +40,8 @@ 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.
|
||||||
|
- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen.
|
||||||
|
- `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 +67,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`
|
||||||
|
|
|
@ -12,8 +12,8 @@ Note: This article is potentially outdated because at this time we may not have
|
||||||
|
|
||||||
### 必要なソフトウェア
|
### 必要なソフトウェア
|
||||||
|
|
||||||
- PostgreSQL 9.6以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
||||||
- `postgresql-contrib` 9.6以上 (同上)
|
- `postgresql-contrib` 11.0以上 (同上)
|
||||||
- Elixir 1.8 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
- Elixir 1.8 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
||||||
- `erlang-dev`
|
- `erlang-dev`
|
||||||
- `erlang-nox`
|
- `erlang-nox`
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
## Required dependencies
|
## Required dependencies
|
||||||
|
|
||||||
* PostgreSQL >=9.6
|
* PostgreSQL >=11.0
|
||||||
* Elixir >=1.11.0 <1.15
|
* Elixir >=1.11.0 <1.15
|
||||||
* Erlang OTP >=22.2.0 (supported: <27)
|
* Erlang OTP >=22.2.0 (supported: <27)
|
||||||
* git
|
* git
|
||||||
|
|
|
@ -119,28 +119,7 @@ def start(_type, _args) do
|
||||||
max_restarts = Application.get_env(:pleroma, __MODULE__)[:max_restarts]
|
max_restarts = Application.get_env(:pleroma, __MODULE__)[:max_restarts]
|
||||||
|
|
||||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
|
opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts]
|
||||||
result = Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
|
|
||||||
set_postgres_server_version()
|
|
||||||
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_postgres_server_version do
|
|
||||||
version =
|
|
||||||
with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"),
|
|
||||||
{num, _} <- Float.parse(version) do
|
|
||||||
num
|
|
||||||
else
|
|
||||||
e ->
|
|
||||||
Logger.warning(
|
|
||||||
"Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6"
|
|
||||||
)
|
|
||||||
|
|
||||||
9.6
|
|
||||||
end
|
|
||||||
|
|
||||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, version)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_custom_modules do
|
def load_custom_modules do
|
||||||
|
|
|
@ -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 @@ def destroy(user_id, activity_id) 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
|
||||||
|
|
|
@ -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
|
|
@ -241,13 +241,13 @@ def find(following_relationships, follower, following) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
For a query with joined activity,
|
For a query with joined activity's actor,
|
||||||
keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user.
|
keeps rows where actor is followed by user -or- is NOT domain-blocked by user.
|
||||||
"""
|
"""
|
||||||
def keep_following_or_not_domain_blocked(query, user) do
|
def keep_following_or_not_domain_blocked(query, user) do
|
||||||
where(
|
where(
|
||||||
query,
|
query,
|
||||||
[_, activity],
|
[_, user_actor: user_actor],
|
||||||
fragment(
|
fragment(
|
||||||
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
|
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
|
||||||
"""
|
"""
|
||||||
|
@ -255,9 +255,9 @@ def keep_following_or_not_domain_blocked(query, user) do
|
||||||
? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
|
? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
|
||||||
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?)
|
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?)
|
||||||
""",
|
""",
|
||||||
activity.actor,
|
user_actor.ap_id,
|
||||||
^user.domain_blocks,
|
^user.domain_blocks,
|
||||||
activity.actor,
|
user_actor.ap_id,
|
||||||
^User.binary_id(user.id),
|
^User.binary_id(user.id),
|
||||||
^accept_state_code()
|
^accept_state_code()
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,12 +40,14 @@ def image_resize(url, options) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Note: video thumbnail is intentionally not resized (always has original dimensions)
|
# Note: video thumbnail is intentionally not resized (always has original dimensions)
|
||||||
|
@spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()}
|
||||||
def video_framegrab(url) do
|
def video_framegrab(url) do
|
||||||
with executable when is_binary(executable) <- System.find_executable("ffmpeg"),
|
with executable when is_binary(executable) <- System.find_executable("ffmpeg"),
|
||||||
{:ok, env} <- HTTP.get(url, [], pool: :media),
|
{:ok, env} <- HTTP.get(url, [], pool: :media),
|
||||||
{:ok, pid} <- StringIO.open(env.body) do
|
{:ok, pid} <- StringIO.open(env.body) do
|
||||||
body_stream = IO.binstream(pid, 1)
|
body_stream = IO.binstream(pid, 1)
|
||||||
|
|
||||||
|
result =
|
||||||
Exile.stream!(
|
Exile.stream!(
|
||||||
[
|
[
|
||||||
executable,
|
executable,
|
||||||
|
@ -62,6 +64,8 @@ def video_framegrab(url) do
|
||||||
stderr: :disable
|
stderr: :disable
|
||||||
)
|
)
|
||||||
|> Enum.into(<<>>)
|
|> Enum.into(<<>>)
|
||||||
|
|
||||||
|
{:ok, result}
|
||||||
else
|
else
|
||||||
nil -> {:error, {:ffmpeg, :command_not_found}}
|
nil -> {:error, {:ffmpeg, :command_not_found}}
|
||||||
{:error, _} = error -> error
|
{:error, _} = error -> error
|
||||||
|
|
|
@ -137,7 +137,7 @@ defp exclude_blocked(query, user, opts) do
|
||||||
blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
|
blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> where([n, a], a.actor not in ^blocked_ap_ids)
|
|> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocked_ap_ids)
|
||||||
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ defp exclude_blockers(query, user) do
|
||||||
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
|
||||||
|
|
||||||
query
|
query
|
||||||
|> where([n, a], a.actor not in ^blocker_ap_ids)
|
|> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocker_ap_ids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ defp exclude_notification_muted(query, user, opts) do
|
||||||
opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
|
opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
|
||||||
|
|
||||||
query
|
query
|
||||||
|> where([n, a], a.actor not in ^notification_muted_ap_ids)
|
|> where([..., user_actor: user_actor], user_actor.ap_id not in ^notification_muted_ap_ids)
|
||||||
|> join(:left, [n, a], tm in ThreadMute,
|
|> join(:left, [n, a], tm in ThreadMute,
|
||||||
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
|
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
|
||||||
as: :thread_mute
|
as: :thread_mute
|
||||||
|
|
|
@ -23,19 +23,12 @@ def search(user, search_query, options \\ []) do
|
||||||
offset = Keyword.get(options, :offset, 0)
|
offset = Keyword.get(options, :offset, 0)
|
||||||
author = Keyword.get(options, :author)
|
author = Keyword.get(options, :author)
|
||||||
|
|
||||||
search_function =
|
|
||||||
if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do
|
|
||||||
:websearch
|
|
||||||
else
|
|
||||||
:plain
|
|
||||||
end
|
|
||||||
|
|
||||||
try do
|
try do
|
||||||
Activity
|
Activity
|
||||||
|> Activity.with_preloaded_object()
|
|> Activity.with_preloaded_object()
|
||||||
|> Activity.restrict_deactivated_users()
|
|> Activity.restrict_deactivated_users()
|
||||||
|> restrict_public(user)
|
|> restrict_public(user)
|
||||||
|> query_with(index_type, search_query, search_function)
|
|> query_with(index_type, search_query, :websearch)
|
||||||
|> maybe_restrict_local(user)
|
|> maybe_restrict_local(user)
|
||||||
|> maybe_restrict_author(author)
|
|> maybe_restrict_author(author)
|
||||||
|> maybe_restrict_blocked(user)
|
|> maybe_restrict_blocked(user)
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.User do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Ecto, only: [assoc: 2]
|
import Ecto, only: [assoc: 2]
|
||||||
|
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
|
||||||
|
|
||||||
alias Ecto.Multi
|
alias Ecto.Multi
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
@ -596,9 +597,23 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
|
|
||||||
defp put_fields(changeset) do
|
defp put_fields(changeset) do
|
||||||
if raw_fields = get_change(changeset, :raw_fields) do
|
if raw_fields = get_change(changeset, :raw_fields) do
|
||||||
|
old_fields = changeset.data.raw_fields
|
||||||
|
|
||||||
raw_fields =
|
raw_fields =
|
||||||
raw_fields
|
raw_fields
|
||||||
|> Enum.filter(fn %{"name" => n} -> n != "" end)
|
|> Enum.filter(fn %{"name" => n} -> n != "" end)
|
||||||
|
|> Enum.map(fn field ->
|
||||||
|
previous =
|
||||||
|
old_fields
|
||||||
|
|> Enum.find(fn %{"value" => value} -> field["value"] == value end)
|
||||||
|
|
||||||
|
if previous && Map.has_key?(previous, "verified_at") do
|
||||||
|
field
|
||||||
|
|> Map.put("verified_at", previous["verified_at"])
|
||||||
|
else
|
||||||
|
field
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
fields =
|
fields =
|
||||||
raw_fields
|
raw_fields
|
||||||
|
@ -1200,6 +1215,10 @@ def update_and_set_cache(struct, params) do
|
||||||
|
|
||||||
def update_and_set_cache(changeset) do
|
def update_and_set_cache(changeset) do
|
||||||
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||||
|
if get_change(changeset, :raw_fields) do
|
||||||
|
BackgroundWorker.enqueue("verify_fields_links", %{"user_id" => user.id})
|
||||||
|
end
|
||||||
|
|
||||||
set_cache(user)
|
set_cache(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1975,8 +1994,45 @@ def perform(:delete, %User{} = user) do
|
||||||
maybe_delete_from_db(user)
|
maybe_delete_from_db(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def perform(:verify_fields_links, user) do
|
||||||
|
profile_urls = [user.ap_id]
|
||||||
|
|
||||||
|
fields =
|
||||||
|
user.raw_fields
|
||||||
|
|> Enum.map(&verify_field_link(&1, profile_urls))
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
user
|
||||||
|
|> update_changeset(%{raw_fields: fields})
|
||||||
|
|
||||||
|
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||||
|
set_cache(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def perform(:set_activation_async, user, status), do: set_activation(user, status)
|
def perform(:set_activation_async, user, status), do: set_activation(user, status)
|
||||||
|
|
||||||
|
defp verify_field_link(field, profile_urls) do
|
||||||
|
verified_at =
|
||||||
|
with %{"value" => value} <- field,
|
||||||
|
{:verified_at, nil} <- {:verified_at, Map.get(field, "verified_at")},
|
||||||
|
%{scheme: scheme, userinfo: nil, host: host}
|
||||||
|
when not_empty_string(host) and scheme in ["http", "https"] <-
|
||||||
|
URI.parse(value),
|
||||||
|
{:not_idn, true} <- {:not_idn, to_string(:idna.encode(host)) == host},
|
||||||
|
"me" <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do
|
||||||
|
CommonUtils.to_masto_date(NaiveDateTime.utc_now())
|
||||||
|
else
|
||||||
|
{:verified_at, value} when not_empty_string(value) ->
|
||||||
|
value
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
Map.put(field, "verified_at", verified_at)
|
||||||
|
end
|
||||||
|
|
||||||
@spec external_users_query() :: Ecto.Query.t()
|
@spec external_users_query() :: Ecto.Query.t()
|
||||||
def external_users_query do
|
def external_users_query do
|
||||||
User.Query.build(%{
|
User.Query.build(%{
|
||||||
|
@ -2664,10 +2720,11 @@ def sanitize_html(%User{} = user) do
|
||||||
# - display name
|
# - display name
|
||||||
def sanitize_html(%User{} = user, filter) do
|
def sanitize_html(%User{} = user, filter) do
|
||||||
fields =
|
fields =
|
||||||
Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
|
Enum.map(user.fields, fn %{"name" => name, "value" => value} = fields ->
|
||||||
%{
|
%{
|
||||||
"name" => name,
|
"name" => name,
|
||||||
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly),
|
||||||
|
"verified_at" => Map.get(fields, "verified_at")
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,14 @@ defp wait_backup(backup, current_processed, task) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json']
|
@files [
|
||||||
|
'actor.json',
|
||||||
|
'outbox.json',
|
||||||
|
'likes.json',
|
||||||
|
'bookmarks.json',
|
||||||
|
'followers.json',
|
||||||
|
'following.json'
|
||||||
|
]
|
||||||
@spec export(Pleroma.User.Backup.t(), pid()) :: {:ok, String.t()} | :error
|
@spec export(Pleroma.User.Backup.t(), pid()) :: {:ok, String.t()} | :error
|
||||||
def export(%__MODULE__{} = backup, caller_pid) do
|
def export(%__MODULE__{} = backup, caller_pid) do
|
||||||
backup = Repo.preload(backup, :user)
|
backup = Repo.preload(backup, :user)
|
||||||
|
@ -207,6 +214,8 @@ def export(%__MODULE__{} = backup, caller_pid) do
|
||||||
:ok <- statuses(dir, backup.user, caller_pid),
|
:ok <- statuses(dir, backup.user, caller_pid),
|
||||||
:ok <- likes(dir, backup.user, caller_pid),
|
:ok <- likes(dir, backup.user, caller_pid),
|
||||||
:ok <- bookmarks(dir, backup.user, caller_pid),
|
:ok <- bookmarks(dir, backup.user, caller_pid),
|
||||||
|
:ok <- followers(dir, backup.user, caller_pid),
|
||||||
|
:ok <- following(dir, backup.user, caller_pid),
|
||||||
{:ok, zip_path} <- :zip.create(backup.file_name, @files, cwd: dir),
|
{:ok, zip_path} <- :zip.create(backup.file_name, @files, cwd: dir),
|
||||||
{:ok, _} <- File.rm_rf(dir) do
|
{:ok, _} <- File.rm_rf(dir) do
|
||||||
{:ok, zip_path}
|
{:ok, zip_path}
|
||||||
|
@ -357,6 +366,16 @@ defp statuses(dir, user, caller_pid) do
|
||||||
caller_pid
|
caller_pid
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp followers(dir, user, caller_pid) do
|
||||||
|
User.get_followers_query(user)
|
||||||
|
|> write(dir, "followers", fn a -> {:ok, a.ap_id} end, caller_pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp following(dir, user, caller_pid) do
|
||||||
|
User.get_friends_query(user)
|
||||||
|
|> write(dir, "following", fn a -> {:ok, a.ap_id} end, caller_pid)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Pleroma.User.Backup.ProcessorAPI do
|
defmodule Pleroma.User.Backup.ProcessorAPI do
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
defp get_author(url) do
|
||||||
|
with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false),
|
||||||
|
%User{ap_id: ap_id, nickname: nickname} <- User.get_cached_by_ap_id(actor) do
|
||||||
|
%{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepend_author(tags, _, false), do: tags
|
||||||
|
|
||||||
|
defp prepend_author(tags, nil, _), do: tags
|
||||||
|
|
||||||
|
defp prepend_author(tags, url, _) do
|
||||||
|
actor = get_author(url)
|
||||||
|
|
||||||
|
if not is_nil(actor) do
|
||||||
|
[actor | tags]
|
||||||
|
else
|
||||||
|
tags
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do
|
||||||
|
tag =
|
||||||
|
tag
|
||||||
|
|> prepend_author(
|
||||||
|
object["inReplyTo"],
|
||||||
|
Config.get([:mrf_force_mention, :mention_parent, true])
|
||||||
|
)
|
||||||
|
|> prepend_author(
|
||||||
|
object["quoteUrl"],
|
||||||
|
Config.get([:mrf_force_mention, :mention_quoted, true])
|
||||||
|
)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
|
{:ok, put_in(activity["object"]["tag"], tag)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
|
end
|
|
@ -12,13 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||||
@primary_key false
|
@primary_key false
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field(:id, :string)
|
field(:id, :string)
|
||||||
field(:type, :string)
|
field(:type, :string, default: "Link")
|
||||||
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
|
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
field(:blurhash, :string)
|
field(:blurhash, :string)
|
||||||
|
|
||||||
embeds_many :url, UrlObjectValidator, primary_key: false do
|
embeds_many :url, UrlObjectValidator, primary_key: false do
|
||||||
field(:type, :string)
|
field(:type, :string, default: "Link")
|
||||||
field(:href, ObjectValidators.Uri)
|
field(:href, ObjectValidators.Uri)
|
||||||
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
|
field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
|
||||||
field(:width, :integer)
|
field(:width, :integer)
|
||||||
|
|
|
@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do
|
||||||
|
|
||||||
embeds_one :replies, Replies, primary_key: false do
|
embeds_one :replies, Replies, primary_key: false do
|
||||||
field(:totalItems, :integer)
|
field(:totalItems, :integer)
|
||||||
field(:type, :string)
|
field(:type, :string, default: "Collection")
|
||||||
end
|
end
|
||||||
|
|
||||||
field(:type, :string)
|
field(:type, :string, default: "Note")
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
|
|
|
@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
|
|
||||||
field(:closed, ObjectValidators.DateTime)
|
field(:closed, ObjectValidators.DateTime)
|
||||||
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
||||||
|
field(:nonAnonymous, :boolean)
|
||||||
embeds_many(:anyOf, QuestionOptionsValidator)
|
embeds_many(:anyOf, QuestionOptionsValidator)
|
||||||
embeds_many(:oneOf, QuestionOptionsValidator)
|
embeds_many(:oneOf, QuestionOptionsValidator)
|
||||||
end
|
end
|
||||||
|
|
|
@ -158,10 +158,10 @@ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp should_federate?(inbox, public) do
|
def should_federate?(nil, _), do: false
|
||||||
if public do
|
def should_federate?(_, true), do: true
|
||||||
true
|
|
||||||
else
|
def should_federate?(inbox, _) do
|
||||||
%{host: host} = URI.parse(inbox)
|
%{host: host} = URI.parse(inbox)
|
||||||
|
|
||||||
quarantined_instances =
|
quarantined_instances =
|
||||||
|
@ -171,7 +171,6 @@ defp should_federate?(inbox, public) do
|
||||||
|
|
||||||
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
@spec recipients(User.t(), Activity.t()) :: [[User.t()]]
|
@spec recipients(User.t(), Activity.t()) :: [[User.t()]]
|
||||||
defp recipients(actor, activity) do
|
defp recipients(actor, activity) do
|
||||||
|
|
|
@ -137,7 +137,8 @@ def spec(opts \\ []) 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 @@ def bookmark_operation 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 @@ def bookmarks_operation 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())
|
||||||
|
|
|
@ -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
|
|
@ -56,6 +56,15 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
description: "Possible answers for the poll."
|
description: "Possible answers for the poll."
|
||||||
|
},
|
||||||
|
pleroma: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
non_anonymous: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "Can voters be publicly identified?"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
|
@ -79,7 +88,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
||||||
votes_count: 4
|
votes_count: 4
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
emojis: []
|
emojis: [],
|
||||||
|
pleroma: %{
|
||||||
|
non_anonymous: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
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 @@ def unpin(
|
||||||
|
|
||||||
@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 @@ def favourites(
|
||||||
@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 =
|
||||||
|
|
|
@ -28,6 +28,7 @@ def render("show.json", _) do
|
||||||
|> to_string,
|
|> to_string,
|
||||||
registrations: Keyword.get(instance, :registrations_open),
|
registrations: Keyword.get(instance, :registrations_open),
|
||||||
approval_required: Keyword.get(instance, :account_approval_required),
|
approval_required: Keyword.get(instance, :account_approval_required),
|
||||||
|
contact_account: contact_account(Keyword.get(instance, :contact_username)),
|
||||||
configuration: configuration(),
|
configuration: configuration(),
|
||||||
# Extra (not present in Mastodon):
|
# Extra (not present in Mastodon):
|
||||||
max_toot_chars: Keyword.get(instance, :limit),
|
max_toot_chars: Keyword.get(instance, :limit),
|
||||||
|
@ -63,11 +64,12 @@ def render("show2.json", _) do
|
||||||
registrations: %{
|
registrations: %{
|
||||||
enabled: Keyword.get(instance, :registrations_open),
|
enabled: Keyword.get(instance, :registrations_open),
|
||||||
approval_required: Keyword.get(instance, :account_approval_required),
|
approval_required: Keyword.get(instance, :account_approval_required),
|
||||||
message: nil
|
message: nil,
|
||||||
|
url: nil
|
||||||
},
|
},
|
||||||
contact: %{
|
contact: %{
|
||||||
email: Keyword.get(instance, :email),
|
email: Keyword.get(instance, :email),
|
||||||
account: nil
|
account: contact_account(Keyword.get(instance, :contact_username))
|
||||||
},
|
},
|
||||||
# Extra (not present in Mastodon):
|
# Extra (not present in Mastodon):
|
||||||
pleroma: pleroma_configuration2(instance)
|
pleroma: pleroma_configuration2(instance)
|
||||||
|
@ -78,7 +80,8 @@ defp common_information(instance) do
|
||||||
%{
|
%{
|
||||||
title: Keyword.get(instance, :name),
|
title: Keyword.get(instance, :name),
|
||||||
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
|
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
|
||||||
languages: Keyword.get(instance, :languages, ["en"])
|
languages: Keyword.get(instance, :languages, ["en"]),
|
||||||
|
rules: []
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -127,7 +130,8 @@ def features do
|
||||||
"profile_directory"
|
"profile_directory"
|
||||||
end,
|
end,
|
||||||
"pleroma:get:main/ostatus",
|
"pleroma:get:main/ostatus",
|
||||||
"pleroma:group_actors"
|
"pleroma:group_actors",
|
||||||
|
"pleroma:bookmark_folders"
|
||||||
]
|
]
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
end
|
end
|
||||||
|
@ -168,15 +172,35 @@ defp fields_limits do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp contact_account(nil), do: nil
|
||||||
|
|
||||||
|
defp contact_account("@" <> username) do
|
||||||
|
contact_account(username)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp contact_account(username) do
|
||||||
|
user = Pleroma.User.get_cached_by_nickname(username)
|
||||||
|
|
||||||
|
if user do
|
||||||
|
Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil})
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp configuration do
|
defp configuration do
|
||||||
%{
|
%{
|
||||||
|
accounts: %{
|
||||||
|
max_featured_tags: 0
|
||||||
|
},
|
||||||
statuses: %{
|
statuses: %{
|
||||||
max_characters: Config.get([:instance, :limit]),
|
max_characters: Config.get([:instance, :limit]),
|
||||||
max_media_attachments: Config.get([:instance, :max_media_attachments])
|
max_media_attachments: Config.get([:instance, :max_media_attachments])
|
||||||
},
|
},
|
||||||
media_attachments: %{
|
media_attachments: %{
|
||||||
image_size_limit: Config.get([:instance, :upload_limit]),
|
image_size_limit: Config.get([:instance, :upload_limit]),
|
||||||
video_size_limit: Config.get([:instance, :upload_limit])
|
video_size_limit: Config.get([:instance, :upload_limit]),
|
||||||
|
supported_mime_types: ["application/octet-stream"]
|
||||||
},
|
},
|
||||||
polls: %{
|
polls: %{
|
||||||
max_options: Config.get([:instance, :poll_limits, :max_options]),
|
max_options: Config.get([:instance, :poll_limits, :max_options]),
|
||||||
|
@ -190,7 +214,13 @@ defp configuration do
|
||||||
defp configuration2 do
|
defp configuration2 do
|
||||||
configuration()
|
configuration()
|
||||||
|> Map.merge(%{
|
|> Map.merge(%{
|
||||||
urls: %{streaming: Pleroma.Web.Endpoint.websocket_url()}
|
urls: %{
|
||||||
|
streaming: Pleroma.Web.Endpoint.websocket_url(),
|
||||||
|
status: Config.get([:instance, :status_page])
|
||||||
|
},
|
||||||
|
vapid: %{
|
||||||
|
public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,10 @@ def render("show.json", %{object: object, multiple: multiple, options: options}
|
||||||
votes_count: votes_count,
|
votes_count: votes_count,
|
||||||
voters_count: voters_count(object),
|
voters_count: voters_count(object),
|
||||||
options: options,
|
options: options,
|
||||||
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
|
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]),
|
||||||
|
pleroma: %{
|
||||||
|
non_anonymous: object.data["nonAnonymous"] || false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params[:for] do
|
if params[:for] do
|
||||||
|
|
|
@ -184,7 +184,14 @@ def render(
|
||||||
|
|
||||||
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 @@ def render(
|
||||||
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 @@ def render(
|
||||||
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 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
|
|
||||||
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 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
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 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
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
|
|
@ -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
|
|
@ -588,6 +588,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
|
||||||
|
|
|
@ -40,6 +40,11 @@ def perform(%Job{
|
||||||
Pleroma.FollowingRelationship.move_following(origin, target)
|
Pleroma.FollowingRelationship.move_following(origin, target)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def perform(%Job{args: %{"op" => "verify_fields_links", "user_id" => user_id}}) do
|
||||||
|
user = User.get_by_id(user_id)
|
||||||
|
User.perform(:verify_fields_links, user)
|
||||||
|
end
|
||||||
|
|
||||||
def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
|
def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
|
||||||
Instance.perform(:delete_instance, host)
|
Instance.perform(:delete_instance, host)
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -40,7 +40,9 @@
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
},
|
},
|
||||||
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
||||||
"formerRepresentations": "litepub:formerRepresentations"
|
"formerRepresentations": "litepub:formerRepresentations",
|
||||||
|
"sm": "http://smithereen.software/ns#",
|
||||||
|
"nonAnonymous": "sm:nonAnonymous"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{"@context":"https://www.w3.org/ns/activitystreams","type":"Note","id":"https://www.minds.com/api/activitypub/users/1198929502760083472/entities/urn:comment:1600926863310458883:0:0:0:1600932467852709903","attributedTo":"https://www.minds.com/api/activitypub/users/1198929502760083472","content":"\u003Ca class=\u0022u-url mention\u0022 href=\u0022https://www.minds.com/lain\u0022 target=\u0022_blank\u0022\u003E@lain\u003C/a\u003E corn syrup.","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://www.minds.com/api/activitypub/users/1198929502760083472/followers","https://lain.com/users/lain"],"tag":[{"type":"Mention","href":"https://www.minds.com/api/activitypub/users/464237775479123984","name":"@lain"}],"url":"https://www.minds.com/newsfeed/1600926863310458883?focusedCommentUrn=urn:comment:1600926863310458883:0:0:0:1600932467852709903","published":"2024-02-04T17:34:03+00:00","inReplyTo":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","source":{"content":"@lain corn syrup.","mediaType":"text/plain"}}
|
|
@ -0,0 +1 @@
|
||||||
|
{"@context":["https://www.w3.org/ns/activitystreams","https://lain.com/schemas/litepub-0.1.jsonld",{"@language":"und"}],"actor":"https://lain.com/users/lain","attachment":[],"attributedTo":"https://lain.com/users/lain","cc":["https://lain.com/users/lain/followers"],"content":"which diet is the best for cognitive dissonance","context":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","conversation":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","id":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","published":"2024-02-04T17:11:23.931890Z","repliesCount":11,"sensitive":null,"source":{"content":"which diet is the best for cognitive dissonance","mediaType":"text/plain"},"summary":"","tag":[],"to":["https://www.w3.org/ns/activitystreams#Public"],"type":"Note"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"type":"Question","id":"https://friends.grishka.me/posts/54642","attributedTo":"https://friends.grishka.me/users/1","content":"<p>здесь тоже можно что-то написать отдельно от опроса</p>","published":"2021-09-04T00:22:16Z","url":"https://friends.grishka.me/posts/54642","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://friends.grishka.me/users/1/followers"],"replies":{"type":"Collection","id":"https://friends.grishka.me/posts/54642/replies","first":{"type":"CollectionPage","items":[],"partOf":"https://friends.grishka.me/posts/54642/replies","next":"https://friends.grishka.me/posts/54642/replies?page=1"}},"sensitive":false,"likes":"https://friends.grishka.me/posts/54642/likes","name":"тестовый опрос","oneOf":[{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/76","name":"тестовый ответ 1","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/76/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/77","name":"тестовый ответ 2","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/77/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/78","name":"тестовый ответ 3","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/78/votes","totalItems":6,"items":[]}}],"votersCount":14,"nonAnonymous":true,"@context":["https://www.w3.org/ns/activitystreams",{"sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#","sm":"http://smithereen.software/ns#","votersCount":"toot:votersCount","nonAnonymous":"sm:nonAnonymous"}]}
|
|
@ -0,0 +1 @@
|
||||||
|
{"type":"Person","id":"https://friends.grishka.me/users/1","name":"Григорий Клюшников","icon":{"type":"Image","image":{"type":"Image","url":"https://friends.grishka.me/i/6QLsOws97AWp5N_osd74C1IC1ijnFopyCBD9MSEeXNQ/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg","width":1280,"height":960},"width":573,"height":572,"cropRegion":[0.26422762870788574,0.3766937553882599,0.7113820910453796,0.9728997349739075],"url":"https://friends.grishka.me/i/ql_49PQcETAWgY_nC-Qj63H_Oa6FyOAEoWFkUSSkUvQ/c:573:572:nowe:338:362/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg"},"summary":"<p>Делаю эту хрень, пытаюсь вырвать социальные сети из жадных лап корпораций</p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p></p>\n<p>This server does NOT support direct messages. Please write me <a href=\"https://t.me/grishka\">on Telegram</a> or <a href=\"https://matrix.to/#/@grishk:matrix.org\">Matrix</a>.</p>","url":"https://friends.grishka.me/grishka","preferredUsername":"grishka","inbox":"https://friends.grishka.me/users/1/inbox","outbox":"https://friends.grishka.me/users/1/outbox","followers":"https://friends.grishka.me/users/1/followers","following":"https://friends.grishka.me/users/1/following","endpoints":{"sharedInbox":"https://friends.grishka.me/activitypub/sharedInbox","collectionSimpleQuery":"https://friends.grishka.me/users/1/collectionQuery"},"publicKey":{"id":"https://friends.grishka.me/users/1#main-key","owner":"https://friends.grishka.me/users/1","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjlakm+i/d9ER/hIeR7KfiFW+SdLZj2SkKIeM8cmR+YFJuh9ghFqXrkFEjcaqUnAFqe5gYDNSQACnDLA8y4DnzjfGNIohKAnRoa9x6GORmfKQvcnjaTZ53S1NvUiPPyc0Pv/vfCtY/Ab0CEXe5BLqL38oZn817Jf7pBrPRTYH7m012kvwAUTT6k0Y8lPITBEG7nzYbbuGcrN9Y/RDdwE08jmBXlZ45bahRH3VNXVpQE17dCzJB+7k+iJ1R7YCoI+DuMlBYGXGE2KVk46NZTuLnOjFV9SyXfWX4/SrJM4oxev+SX2N75tQgmNZmVVHeqg2ZcbC0WCfNjJOi2HHS9MujwIDAQAB\n-----END PUBLIC KEY-----\n"},"wall":"https://friends.grishka.me/users/1/wall","firstName":"Григорий","lastName":"Клюшников","middleName":"Александрович","vcard:bday":"1993-01-22","gender":"http://schema.org#Male","supportsFriendRequests":true,"friends":"https://friends.grishka.me/users/1/friends","groups":"https://friends.grishka.me/users/1/groups","capabilities":{"supportsFriendRequests":true},"@context":["https://www.w3.org/ns/activitystreams",{"sm":"http://smithereen.software/ns#","cropRegion":{"@id":"sm:cropRegion","@container":"@list"},"wall":{"@id":"sm:wall","@type":"@id"},"collectionSimpleQuery":"sm:collectionSimpleQuery","sc":"http://schema.org#","firstName":"sc:givenName","lastName":"sc:familyName","middleName":"sc:additionalName","gender":{"@id":"sc:gender","@type":"sc:GenderType"},"maidenName":"sm:maidenName","friends":{"@id":"sm:friends","@type":"@id"},"groups":{"@id":"sm:groups","@type":"@id"},"vcard":"http://www.w3.org/2006/vcard/ns#","capabilities":"litepub:capabilities","supportsFriendRequests":"sm:supportsFriendRequests","litepub":"http://litepub.social/ns#"},"https://w3id.org/security/v1"]}
|
|
@ -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 @@ test "with invalid params" 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
|
||||||
|
|
|
@ -35,21 +35,6 @@ test "it does not find local-only posts for anonymous users" do
|
||||||
assert [] = Search.search(nil, "wednesday")
|
assert [] = Search.search(nil, "wednesday")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "using plainto_tsquery on postgres < 11" do
|
|
||||||
old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
|
|
||||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
|
|
||||||
on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end)
|
|
||||||
|
|
||||||
user = insert(:user)
|
|
||||||
{:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
|
||||||
{:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"})
|
|
||||||
|
|
||||||
# plainto doesn't understand complex queries
|
|
||||||
assert [result] = Search.search(nil, "wednesday -dudes")
|
|
||||||
|
|
||||||
assert result.id == post.id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "using websearch_to_tsquery" do
|
test "using websearch_to_tsquery" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
{:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"})
|
||||||
|
|
|
@ -166,6 +166,7 @@ test "it removes outdated backups after creating a fresh one" do
|
||||||
|
|
||||||
test "it creates a zip archive with user data" do
|
test "it creates a zip archive with user data" do
|
||||||
user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"})
|
user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"})
|
||||||
|
%{ap_id: other_ap_id} = other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, %{object: %{data: %{"id" => id1}}} = status1} =
|
{:ok, %{object: %{data: %{"id" => id1}}} = status1} =
|
||||||
CommonAPI.post(user, %{status: "status1"})
|
CommonAPI.post(user, %{status: "status1"})
|
||||||
|
@ -182,6 +183,8 @@ test "it creates a zip archive with user data" do
|
||||||
Bookmark.create(user.id, status2.id)
|
Bookmark.create(user.id, status2.id)
|
||||||
Bookmark.create(user.id, status3.id)
|
Bookmark.create(user.id, status3.id)
|
||||||
|
|
||||||
|
CommonAPI.follow(user, other_user)
|
||||||
|
|
||||||
assert {:ok, backup} = user |> Backup.new() |> Repo.insert()
|
assert {:ok, backup} = user |> Backup.new() |> Repo.insert()
|
||||||
assert {:ok, path} = Backup.export(backup, self())
|
assert {:ok, path} = Backup.export(backup, self())
|
||||||
assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory])
|
assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory])
|
||||||
|
@ -261,6 +264,16 @@ test "it creates a zip archive with user data" do
|
||||||
"type" => "OrderedCollection"
|
"type" => "OrderedCollection"
|
||||||
} = Jason.decode!(json)
|
} = Jason.decode!(json)
|
||||||
|
|
||||||
|
assert {:ok, {'following.json', json}} = :zip.zip_get('following.json', zipfile)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"id" => "following.json",
|
||||||
|
"orderedItems" => [^other_ap_id],
|
||||||
|
"totalItems" => 1,
|
||||||
|
"type" => "OrderedCollection"
|
||||||
|
} = Jason.decode!(json)
|
||||||
|
|
||||||
:zip.zip_close(zipfile)
|
:zip.zip_close(zipfile)
|
||||||
File.rm!(path)
|
File.rm!(path)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2928,4 +2928,51 @@ test "it doesn't pin users you do not follow" do
|
||||||
refute User.endorses?(user, pinned_user)
|
refute User.endorses?(user, pinned_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it checks fields links for a backlink" do
|
||||||
|
user = insert(:user, ap_id: "https://social.example.org/users/lain")
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
%{"name" => "Link", "value" => "http://example.com/rel_me/null"},
|
||||||
|
%{"name" => "Verified link", "value" => "http://example.com/rel_me/link"},
|
||||||
|
%{"name" => "Not a link", "value" => "i'm not a link"}
|
||||||
|
]
|
||||||
|
|
||||||
|
user
|
||||||
|
|> User.update_and_set_cache(%{raw_fields: fields})
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{"verified_at" => nil},
|
||||||
|
%{"verified_at" => verified_at},
|
||||||
|
%{"verified_at" => nil}
|
||||||
|
] = user.fields
|
||||||
|
|
||||||
|
assert is_binary(verified_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updating fields does not invalidate previously validated links" do
|
||||||
|
user = insert(:user, ap_id: "https://social.example.org/users/lain")
|
||||||
|
|
||||||
|
user
|
||||||
|
|> User.update_and_set_cache(%{
|
||||||
|
raw_fields: [%{"name" => "verified link", "value" => "http://example.com/rel_me/link"}]
|
||||||
|
})
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
%User{fields: [%{"verified_at" => verified_at}]} = user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
user
|
||||||
|
|> User.update_and_set_cache(%{
|
||||||
|
raw_fields: [%{"name" => "Verified link", "value" => "http://example.com/rel_me/link"}]
|
||||||
|
})
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
assert [%{"verified_at" => ^verified_at}] = user.fields
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.ForceMention
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "adds mention to a reply" do
|
||||||
|
lain =
|
||||||
|
insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false)
|
||||||
|
|
||||||
|
niobleoum =
|
||||||
|
insert(:user,
|
||||||
|
ap_id: "https://www.minds.com/api/activitypub/users/1198929502760083472",
|
||||||
|
nickname: "niobleoum@minds.com",
|
||||||
|
local: false
|
||||||
|
)
|
||||||
|
|
||||||
|
status = File.read!("test/fixtures/minds-pleroma-mentioned-post.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
status_activity = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => lain.ap_id,
|
||||||
|
"object" => status
|
||||||
|
}
|
||||||
|
|
||||||
|
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity)
|
||||||
|
|
||||||
|
reply = File.read!("test/fixtures/minds-invalid-mention-post.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
reply_activity = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => niobleoum.ap_id,
|
||||||
|
"object" => reply
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(reply_activity)
|
||||||
|
|
||||||
|
assert Enum.find(tag, fn %{"href" => href} -> href == lain.ap_id end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "adds mention to a quote" do
|
||||||
|
user1 = insert(:user, ap_id: "https://misskey.io/users/83ssedkv53")
|
||||||
|
user2 = insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
|
||||||
|
|
||||||
|
status = File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
status_activity = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => user1.ap_id,
|
||||||
|
"object" => status
|
||||||
|
}
|
||||||
|
|
||||||
|
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity)
|
||||||
|
|
||||||
|
quote_post = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
|
||||||
|
|
||||||
|
quote_activity = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => user2.ap_id,
|
||||||
|
"object" => quote_post
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(quote_activity)
|
||||||
|
|
||||||
|
assert Enum.find(tag, fn %{"href" => href} -> href == user1.ap_id end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,6 +25,17 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
|
|
||||||
setup_all do: clear_config([:instance, :federating], true)
|
setup_all do: clear_config([:instance, :federating], true)
|
||||||
|
|
||||||
|
describe "should_federate?/1" do
|
||||||
|
test "it returns false when the inbox is nil" do
|
||||||
|
refute Publisher.should_federate?(nil, false)
|
||||||
|
refute Publisher.should_federate?(nil, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns true when public is true" do
|
||||||
|
assert Publisher.should_federate?(false, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "gather_webfinger_links/1" do
|
describe "gather_webfinger_links/1" do
|
||||||
test "it returns links" do
|
test "it returns links" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
@ -205,6 +216,7 @@ test "publish to url with with different ports" do
|
||||||
refute called(Instances.set_reachable(inbox))
|
refute called(Instances.set_reachable(inbox))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag capture_log: true
|
||||||
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
|
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
|
|
|
@ -107,6 +107,18 @@ test "instance languages", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "get instance contact information", %{conn: conn} do
|
||||||
|
user = insert(:user, %{local: true})
|
||||||
|
|
||||||
|
clear_config([:instance, :contact_username], user.nickname)
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/instance")
|
||||||
|
|
||||||
|
assert result = json_response_and_validate_schema(conn, 200)
|
||||||
|
|
||||||
|
assert result["contact_account"]["id"] == user.id
|
||||||
|
end
|
||||||
|
|
||||||
test "get instance information v2", %{conn: conn} do
|
test "get instance information v2", %{conn: conn} do
|
||||||
clear_config([:auth, :oauth_consumer_strategies], [])
|
clear_config([:auth, :oauth_consumer_strategies], [])
|
||||||
|
|
||||||
|
|
|
@ -322,26 +322,20 @@ test "search", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
|
test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
|
||||||
old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
|
|
||||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
|
|
||||||
on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end)
|
|
||||||
|
|
||||||
capture_log(fn ->
|
|
||||||
{:ok, %{id: activity_id}} =
|
{:ok, %{id: activity_id}} =
|
||||||
CommonAPI.post(insert(:user), %{
|
CommonAPI.post(insert(:user), %{
|
||||||
status: "check out http://mastodon.example.org/@admin/99541947525187367"
|
status: "check out http://mastodon.example.org/@admin/99541947525187367"
|
||||||
})
|
})
|
||||||
|
|
||||||
results =
|
%{"url" => result_url, "id" => result_id} =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367")
|
|> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|> Map.get("statuses")
|
||||||
|
|> List.first()
|
||||||
|
|
||||||
assert [
|
refute match?(^result_id, activity_id)
|
||||||
%{"url" => "http://mastodon.example.org/@admin/99541947525187367"},
|
assert match?(^result_url, "http://mastodon.example.org/@admin/99541947525187367")
|
||||||
%{"id" => ^activity_id}
|
|
||||||
] = results["statuses"]
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
||||||
|
|
|
@ -1828,6 +1828,60 @@ test "bookmarks" 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"])
|
||||||
|
|
||||||
|
|
|
@ -511,10 +511,15 @@ test "update fields", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert account_data["fields"] == [
|
assert account_data["fields"] == [
|
||||||
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
|
%{
|
||||||
|
"name" => "<a href=\"http://google.com\">foo</a>",
|
||||||
|
"value" => "bar",
|
||||||
|
"verified_at" => nil
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
"name" => "link.io",
|
"name" => "link.io",
|
||||||
"value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
|
"value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>),
|
||||||
|
"verified_at" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -573,8 +578,8 @@ test "emojis in fields labels", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert account_data["fields"] == [
|
assert account_data["fields"] == [
|
||||||
%{"name" => ":firefox:", "value" => "is best 2hu"},
|
%{"name" => ":firefox:", "value" => "is best 2hu", "verified_at" => nil},
|
||||||
%{"name" => "they wins", "value" => ":blank:"}
|
%{"name" => "they wins", "value" => ":blank:", "verified_at" => nil}
|
||||||
]
|
]
|
||||||
|
|
||||||
assert account_data["source"]["fields"] == [
|
assert account_data["source"]["fields"] == [
|
||||||
|
@ -602,10 +607,11 @@ test "update fields via x-www-form-urlencoded", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert account["fields"] == [
|
assert account["fields"] == [
|
||||||
%{"name" => "foo", "value" => "bar"},
|
%{"name" => "foo", "value" => "bar", "verified_at" => nil},
|
||||||
%{
|
%{
|
||||||
"name" => "link",
|
"name" => "link",
|
||||||
"value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
|
"value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>),
|
||||||
|
"verified_at" => nil
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -627,7 +633,7 @@ test "update fields with empty name", %{conn: conn} do
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
assert account["fields"] == [
|
assert account["fields"] == [
|
||||||
%{"name" => "foo", "value" => ""}
|
%{"name" => "foo", "value" => "", "verified_at" => nil}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,8 @@ test "renders a poll" do
|
||||||
%{title: "why are you even asking?", votes_count: 0}
|
%{title: "why are you even asking?", votes_count: 0}
|
||||||
],
|
],
|
||||||
votes_count: 0,
|
votes_count: 0,
|
||||||
voters_count: 0
|
voters_count: 0,
|
||||||
|
pleroma: %{non_anonymous: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = PollView.render("show.json", %{object: object})
|
result = PollView.render("show.json", %{object: object})
|
||||||
|
@ -165,4 +166,11 @@ test "doesn't strips HTML tags" do
|
||||||
]
|
]
|
||||||
} = PollView.render("show.json", %{object: object})
|
} = PollView.render("show.json", %{object: object})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "that poll is non anonymous" do
|
||||||
|
object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true)
|
||||||
|
result = PollView.render("show.json", %{object: object})
|
||||||
|
|
||||||
|
assert result[:pleroma][:non_anonymous] == true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -341,7 +341,8 @@ test "a note activity" 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
|
|
@ -1503,6 +1503,24 @@ def get("https://example.com/empty", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: "hello"}}
|
{:ok, %Tesla.Env{status: 200, body: "hello"}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://friends.grishka.me/posts/54642", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://friends.grishka.me/users/1", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/smithereen_user.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(url, query, body, headers) do
|
def get(url, query, body, headers) do
|
||||||
{:error,
|
{:error,
|
||||||
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
|
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
Code.put_compiler_option(:warnings_as_errors, true)
|
Code.put_compiler_option(:warnings_as_errors, true)
|
||||||
|
|
||||||
|
ExUnit.configure(max_cases: System.schedulers_online())
|
||||||
|
|
||||||
ExUnit.start(exclude: [:federated, :erratic])
|
ExUnit.start(exclude: [:federated, :erratic])
|
||||||
|
|
||||||
if match?({:unix, :darwin}, :os.type()) do
|
if match?({:unix, :darwin}, :os.type()) do
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Git will ignore everything in this directory except this file.
|
|
||||||
*
|
|
||||||
!.gitignore
|
|
Loading…
Reference in New Issue