- mix deps.get
- mix compile --force
+spec-build:
+ stage: test
+ artifacts:
+ paths:
+ - spec.json
+ script:
+ - mix pleroma.openapi_spec spec.json
+
benchmark:
stage: benchmark
when: manual
- (ssh -t dokku@pleroma.online -- certs:add "$CI_ENVIRONMENT_SLUG" /home/dokku/server.crt /home/dokku/server.key) || true
- git push -f dokku@pleroma.online:$CI_ENVIRONMENT_SLUG $CI_COMMIT_SHA:refs/heads/master
+spec-deploy:
+ stage: deploy
+ artifacts:
+ paths:
+ - spec.json
+ only:
+ - develop@pleroma/pleroma
+ image: alpine:latest
+ before_script:
+ - apk add curl
+ script:
+ - curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline
+
+
stop_review_app:
image: alpine:3.9
stage: deploy
- **Breaking**: Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm`
- **Breaking**: Changed `mix pleroma.user toggle_activated` to `mix pleroma.user activate/deactivate`
-- **Breaking**: AdminAPI changed User field `confirmation_pending` to `is_confirmed`
-- **Breaking**: AdminAPI changed User field `approval_pending` to `is_approved`
-- **Breaking**: AdminAPI changed User field `deactivated` to `is_active`
- Polls now always return a `voters_count`, even if they are single-choice.
- Admin Emails: The ap id is used as the user link in emails now.
- Improved registration workflow for email confirmation and account approval modes.
- Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries.
- Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators.
-- Admin API: Reports now ordered by newest
- Deprecated `Pleroma.Uploaders.S3, :public_endpoint`. Now `Pleroma.Upload, :base_url` is the standard configuration key for all uploaders.
- Improved Apache webserver support: updated sample configuration, MediaProxy cache invalidation verified with the included sample script
- Improve OAuth 2.0 provider support. A missing `fqn` field was added to the response, but does not expose the user's email address.
- Provide redirect of external posts from `/notice/:id` to their original URL
+<details>
+ <summary>API Changes</summary>
+
+- **Breaking:** AdminAPI changed User field `confirmation_pending` to `is_confirmed`
+- **Breaking:** AdminAPI changed User field `approval_pending` to `is_approved`
+- **Breaking**: AdminAPI changed User field `deactivated` to `is_active`
+- **Breaking:** AdminAPI `GET /api/pleroma/admin/users/:nickname_or_id/statuses` changed response format and added the number of total users posts.
+- **Breaking:** AdminAPI `GET /api/pleroma/admin/instances/:instance/statuses` changed response format and added the number of total users posts.
+- Admin API: Reports now ordered by newest
+
+</details>
+
### Added
- Reports now generate notifications for admins and mods.
- Mastodon API: Fixed last_status.account being not filled with account data.
- Mastodon API: Fix not being able to add or remove multiple users at once in lists.
- Mastodon API: Fixed own_votes being not returned with poll data.
+ - Mastodon API: Fixed creation of scheduled posts with polls.
+ - Mastodon API: Support for expires_in/expires_at in the Filters.
</details>
## Unreleased (Patch)
end
defp create_filter(user) do
- Pleroma.Filter.create(%Pleroma.Filter{
+ Pleroma.Filter.create(%{
user_id: user.id,
phrase: "must be filtered",
- hide: true
+ hide: true,
+ context: ["home"]
})
end
queues: [
activity_expiration: 10,
token_expiration: 5,
+ filter_expiration: 1,
backup: 1,
federator_incoming: 50,
federator_outgoing: 50,
firefox, /emoji/Firefox.gif, Gif,Fun
blank, /emoji/blank.png, Fun
dinosaur, /emoji/dino walking.gif, Gif
-external_emoji, https://example.com/emoji.png
- *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false)
- Response:
- On failure: `Not found`
- - On success: JSON array of user's latest statuses
+ - On success: JSON, where:
+ - `total`: total count of the statuses for the user
+ - `activities`: list of the statuses for the user
+
+```json
+{
+ "total" : 1,
+ "activities": [
+ // activities list
+ ]
+}
+```
## `GET /api/pleroma/admin/instances/:instance/statuses`
- *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false)
- Response:
- On failure: `Not found`
- - On success: JSON array of instance's latest statuses
+ - On success: JSON, where:
+ - `total`: total count of the statuses for the instance
+ - `activities`: list of the statuses for the instance
+
+```json
+{
+ "total" : 1,
+ "activities": [
+ // activities list
+ ]
+}
+```
## `GET /api/pleroma/admin/statuses`
--- /dev/null
+defmodule Mix.Tasks.Pleroma.OpenapiSpec do
+ def run([path]) do
+ spec = Pleroma.Web.ApiSpec.spec(server_specific: false) |> Jason.encode!()
+ File.write(path, spec)
+ end
+end
# it should run even if there are no emoji packs
shortcode_globs = Config.get([:emoji, :shortcode_globs], [])
+ # for testing emoji.txt entries we do not want exposed in normal operation
+ test_emoji =
+ if Mix.env() == :test do
+ load_from_file("test/config/emoji.txt", emoji_groups)
+ else
+ []
+ end
+
emojis_txt =
(load_from_file("config/emoji.txt", emoji_groups) ++
load_from_file("config/custom_emoji.txt", emoji_groups) ++
- load_from_globs(shortcode_globs, emoji_groups))
+ load_from_globs(shortcode_globs, emoji_groups) ++
+ test_emoji)
|> Enum.reject(fn value -> value == nil end)
Enum.map(emojis ++ emojis_txt, &prepare_emoji/1)
alias Pleroma.Repo
alias Pleroma.User
+ @type t() :: %__MODULE__{}
+ @type format() :: :postgres | :re
+
schema "filters" do
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
field(:filter_id, :integer)
field(:whole_word, :boolean, default: true)
field(:phrase, :string)
field(:context, {:array, :string})
- field(:expires_at, :utc_datetime)
+ field(:expires_at, :naive_datetime)
timestamps()
end
+ @spec get(integer() | String.t(), User.t()) :: t() | nil
def get(id, %{id: user_id} = _user) do
query =
from(
- f in Pleroma.Filter,
+ f in __MODULE__,
where: f.filter_id == ^id,
where: f.user_id == ^user_id
)
Repo.one(query)
end
+ @spec get_active(Ecto.Query.t() | module()) :: Ecto.Query.t()
def get_active(query) do
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
end
+ @spec get_irreversible(Ecto.Query.t()) :: Ecto.Query.t()
def get_irreversible(query) do
from(f in query, where: f.hide)
end
+ @spec get_filters(Ecto.Query.t() | module(), User.t()) :: [t()]
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
query =
from(
Repo.all(query)
end
- def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do
+ @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def create(attrs \\ %{}) do
+ Repo.transaction(fn -> create_with_expiration(attrs) end)
+ end
+
+ defp create_with_expiration(attrs) do
+ with {:ok, filter} <- do_create(attrs),
+ {:ok, _} <- maybe_add_expiration_job(filter) do
+ filter
+ else
+ {:error, error} -> Repo.rollback(error)
+ end
+ end
+
+ defp do_create(attrs) do
+ %__MODULE__{}
+ |> cast(attrs, [:phrase, :context, :hide, :expires_at, :whole_word, :user_id, :filter_id])
+ |> maybe_add_filter_id()
+ |> validate_required([:phrase, :context, :user_id, :filter_id])
+ |> maybe_add_expires_at(attrs)
+ |> Repo.insert()
+ end
+
+ defp maybe_add_filter_id(%{changes: %{filter_id: _}} = changeset), do: changeset
+
+ defp maybe_add_filter_id(%{changes: %{user_id: user_id}} = changeset) do
# If filter_id wasn't given, use the max filter_id for this user plus 1.
# XXX This could result in a race condition if a user tries to add two
# different filters for their account from two different clients at the
max_id_query =
from(
- f in Pleroma.Filter,
+ f in __MODULE__,
where: f.user_id == ^user_id,
select: max(f.filter_id)
)
max_id + 1
end
- filter
- |> Map.put(:filter_id, filter_id)
- |> Repo.insert()
+ change(changeset, filter_id: filter_id)
+ end
+
+ # don't override expires_at, if passed expires_at and expires_in
+ defp maybe_add_expires_at(%{changes: %{expires_at: %NaiveDateTime{} = _}} = changeset, _) do
+ changeset
end
- def create(%Pleroma.Filter{} = filter) do
- Repo.insert(filter)
+ defp maybe_add_expires_at(changeset, %{expires_in: expires_in})
+ when is_integer(expires_in) and expires_in > 0 do
+ expires_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(expires_in)
+ |> NaiveDateTime.truncate(:second)
+
+ change(changeset, expires_at: expires_at)
end
- def delete(%Pleroma.Filter{id: filter_key} = filter) when is_number(filter_key) do
- Repo.delete(filter)
+ defp maybe_add_expires_at(changeset, %{expires_in: nil}) do
+ change(changeset, expires_at: nil)
end
- def delete(%Pleroma.Filter{id: filter_key} = filter) when is_nil(filter_key) do
- %Pleroma.Filter{id: id} = get(filter.filter_id, %{id: filter.user_id})
+ defp maybe_add_expires_at(changeset, _), do: changeset
- filter
- |> Map.put(:id, id)
- |> Repo.delete()
+ defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do
+ Pleroma.Workers.PurgeExpiredFilter.enqueue(%{
+ filter_id: filter.id,
+ expires_at: DateTime.from_naive!(expires_at, "Etc/UTC")
+ })
end
- def update(%Pleroma.Filter{} = filter, params) do
+ defp maybe_add_expiration_job(_), do: {:ok, nil}
+
+ @spec delete(t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def delete(%__MODULE__{} = filter) do
+ Repo.transaction(fn -> delete_with_expiration(filter) end)
+ end
+
+ defp delete_with_expiration(filter) do
+ with {:ok, _} <- maybe_delete_old_expiration_job(filter, nil),
+ {:ok, filter} <- Repo.delete(filter) do
+ filter
+ else
+ {:error, error} -> Repo.rollback(error)
+ end
+ end
+
+ @spec update(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+ def update(%__MODULE__{} = filter, params) do
+ Repo.transaction(fn -> update_with_expiration(filter, params) end)
+ end
+
+ defp update_with_expiration(filter, params) do
+ with {:ok, updated} <- do_update(filter, params),
+ {:ok, _} <- maybe_delete_old_expiration_job(filter, updated),
+ {:ok, _} <-
+ maybe_add_expiration_job(updated) do
+ updated
+ else
+ {:error, error} -> Repo.rollback(error)
+ end
+ end
+
+ defp do_update(filter, params) do
filter
|> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word])
|> validate_required([:phrase, :context])
+ |> maybe_add_expires_at(params)
|> Repo.update()
end
+ defp maybe_delete_old_expiration_job(%{expires_at: nil}, _), do: {:ok, nil}
+
+ defp maybe_delete_old_expiration_job(%{expires_at: expires_at}, %{expires_at: expires_at}) do
+ {:ok, nil}
+ end
+
+ defp maybe_delete_old_expiration_job(%{id: id}, _) do
+ with %Oban.Job{} = job <- Pleroma.Workers.PurgeExpiredFilter.get_expiration(id) do
+ Repo.delete(job)
+ else
+ nil -> {:ok, nil}
+ end
+ end
+
+ @spec compose_regex(User.t() | [t()], format()) :: String.t() | Regex.t() | nil
def compose_regex(user_or_filters, format \\ :postgres)
def compose_regex(%User{} = user, format) do
|> Enum.reverse()
end
- def fetch_user_activities(user, reading_user, params \\ %{}) do
+ def fetch_user_activities(user, reading_user, params \\ %{})
+
+ def fetch_user_activities(user, reading_user, %{total: true} = params) do
+ result = fetch_activities_for_user(user, reading_user, params)
+
+ Keyword.put(result, :items, Enum.reverse(result[:items]))
+ end
+
+ def fetch_user_activities(user, reading_user, params) do
+ user
+ |> fetch_activities_for_user(reading_user, params)
+ |> Enum.reverse()
+ end
+
+ defp fetch_activities_for_user(user, reading_user, params) do
params =
params
|> Map.put(:type, ["Create", "Announce"])
}
|> user_activities_recipients()
|> fetch_activities(params, pagination_type)
- |> Enum.reverse()
+ end
+
+ def fetch_statuses(reading_user, %{total: true} = params) do
+ result = fetch_activities_for_reading_user(reading_user, params)
+ Keyword.put(result, :items, Enum.reverse(result[:items]))
end
def fetch_statuses(reading_user, params) do
+ reading_user
+ |> fetch_activities_for_reading_user(params)
+ |> Enum.reverse()
+ end
+
+ defp fetch_activities_for_reading_user(reading_user, params) do
params = Map.put(params, :type, ["Create", "Announce"])
%{
}
|> user_activities_recipients()
|> fetch_activities(params, :offset)
- |> Enum.reverse()
end
defp user_activities_recipients(%{godmode: true}), do: []
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
- activities =
+ result =
ActivityPub.fetch_statuses(nil, %{
instance: instance,
limit: page_size,
offset: (page - 1) * page_size,
- exclude_reblogs: not with_reblogs
+ exclude_reblogs: not with_reblogs,
+ total: true
})
conn
|> put_view(AdminAPI.StatusView)
- |> render("index.json", %{activities: activities, as: :activity})
+ |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
end
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
{page, page_size} = page_params(params)
- activities =
+ result =
ActivityPub.fetch_user_activities(user, nil, %{
limit: page_size,
offset: (page - 1) * page_size,
godmode: godmode,
exclude_reblogs: not with_reblogs,
- pagination_type: :offset
+ pagination_type: :offset,
+ total: true
})
conn
|> put_view(AdminAPI.StatusView)
- |> render("index.json", %{activities: activities, as: :activity})
+ |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
else
_ -> {:error, :not_found}
end
defdelegate merge_account_views(user), to: AdminAPI.AccountView
+ def render("index.json", %{total: total} = opts) do
+ %{total: total, activities: safe_render_many(opts.activities, __MODULE__, "show.json", opts)}
+ end
+
def render("index.json", opts) do
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
end
@behaviour OpenApi
@impl OpenApi
- def spec do
+ def spec(opts \\ []) do
%OpenApi{
servers:
- if Phoenix.Endpoint.server?(:pleroma, Endpoint) do
+ if opts[:server_specific] do
[
# Populate the Server info from a phoenix endpoint
OpenApiSpex.Server.from_endpoint(Endpoint)
[]
end,
info: %OpenApiSpex.Info{
- title: "Pleroma",
- description: Application.spec(:pleroma, :description) |> to_string(),
- version: Application.spec(:pleroma, :vsn) |> to_string()
+ title: "Pleroma API",
+ description: """
+ This is documentation for client Pleroma API. Most of the endpoints and entities come
+ from Mastodon API and have custom extensions on top.
+
+ While this document aims to be a complete guide to the client API Pleroma exposes,
+ the details are still being worked out. Some endpoints may have incomplete or poorly worded documentation.
+ You might want to check the following resources if something is not clear:
+ - [Legacy Pleroma-specific endpoint documentation](https://docs-develop.pleroma.social/backend/development/API/pleroma_api/)
+ - [Mastodon API documentation](https://docs.joinmastodon.org/client/intro/)
+ - [Differences in Mastodon API responses from vanilla Mastodon](https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/)
+
+ Please report such occurences on our [issue tracker](https://git.pleroma.social/pleroma/pleroma/-/issues). Feel free to submit API questions or proposals there too!
+ """,
+ version: Application.spec(:pleroma, :vsn) |> to_string(),
+ extensions: %{
+ # Logo path should be picked so that the path exists both on Pleroma instances and on api.pleroma.social
+ "x-logo": %{"url" => "/static/logo.svg", "altText" => "Pleroma logo"}
+ }
},
# populate the paths from a phoenix router
paths: OpenApiSpex.Paths.from_router(Router),
authorizationUrl: "/oauth/authorize",
tokenUrl: "/oauth/token",
scopes: %{
- "read" => "read",
- "write" => "write",
- "follow" => "follow",
- "push" => "push"
+ # TODO: Document granular scopes
+ "read" => "Read everything",
+ "write" => "Write everything",
+ "follow" => "Manage relationships",
+ "push" => "Web Push API subscriptions"
}
}
}
}
}
+ },
+ extensions: %{
+ # Redoc-specific extension, every time a new tag is added it should be reflected here,
+ # otherwise it won't be shown.
+ "x-tagGroups": [
+ %{
+ "name" => "Accounts",
+ "tags" => ["Account actions", "Retrieve account information", "Scrobbles"]
+ },
+ %{
+ "name" => "Administration",
+ "tags" => [
+ "Chat administration",
+ "Emoji packs",
+ "Frontend managment",
+ "Instance configuration",
+ "Instance documents",
+ "Invites",
+ "MediaProxy cache",
+ "OAuth application managment",
+ "Report managment",
+ "Relays",
+ "Status administration"
+ ]
+ },
+ %{"name" => "Applications", "tags" => ["Applications", "Push subscriptions"]},
+ %{
+ "name" => "Current account",
+ "tags" => [
+ "Account credentials",
+ "Backups",
+ "Blocks and mutes",
+ "Data import",
+ "Domain blocks",
+ "Follow requests",
+ "Mascot",
+ "Markers",
+ "Notifications"
+ ]
+ },
+ %{"name" => "Instance", "tags" => ["Custom emojis"]},
+ %{"name" => "Messaging", "tags" => ["Chats", "Conversations"]},
+ %{
+ "name" => "Statuses",
+ "tags" => [
+ "Emoji reactions",
+ "Lists",
+ "Polls",
+ "Timelines",
+ "Retrieve status information",
+ "Scheduled statuses",
+ "Search",
+ "Status actions"
+ ]
+ },
+ %{"name" => "Miscellaneous", "tags" => ["Reports", "Suggestions"]}
+ ]
}
}
# discover request/response schemas from path specs
:with_relationships,
:query,
BooleanLike,
- "Embed relationships into accounts."
+ "Embed relationships into accounts. **If this parameter is not set account's `pleroma.relationship` is going to be `null`.**"
)
end
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account credentials"],
summary: "Register an account",
description:
"Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
def verify_credentials_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account credentials"],
description: "Test to make sure that the user token works.",
summary: "Verify account credentials",
operationId: "AccountController.verify_credentials",
def update_credentials_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account credentials"],
summary: "Update account credentials",
description: "Update the user's display and preferences.",
operationId: "AccountController.update_credentials",
def relationships_operation do
%Operation{
- tags: ["accounts"],
- summary: "Check relationships to other accounts",
+ tags: ["Retrieve account information"],
+ summary: "Relationship with current account",
operationId: "AccountController.relationships",
description: "Find out whether a given account is followed, blocked, muted, etc.",
security: [%{"oAuth" => ["read:follows"]}],
def show_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Account",
operationId: "AccountController.show",
description: "View information about a profile.",
def statuses_operation do
%Operation{
- tags: ["accounts"],
summary: "Statuses",
+ tags: ["Retrieve account information"],
operationId: "AccountController.statuses",
description:
"Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
def followers_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Followers",
operationId: "AccountController.followers",
security: [%{"oAuth" => ["read:accounts"]}],
def following_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Following",
operationId: "AccountController.following",
security: [%{"oAuth" => ["read:accounts"]}],
def lists_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Lists containing this account",
operationId: "AccountController.lists",
security: [%{"oAuth" => ["read:lists"]}],
def follow_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Follow",
operationId: "AccountController.follow",
security: [%{"oAuth" => ["follow", "write:follows"]}],
def unfollow_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Unfollow",
operationId: "AccountController.unfollow",
security: [%{"oAuth" => ["follow", "write:follows"]}],
def mute_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Mute",
operationId: "AccountController.mute",
security: [%{"oAuth" => ["follow", "write:mutes"]}],
def unmute_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Unmute",
operationId: "AccountController.unmute",
security: [%{"oAuth" => ["follow", "write:mutes"]}],
def block_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Block",
operationId: "AccountController.block",
security: [%{"oAuth" => ["follow", "write:blocks"]}],
def unblock_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Unblock",
operationId: "AccountController.unblock",
security: [%{"oAuth" => ["follow", "write:blocks"]}],
def follow_by_uri_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Account actions"],
summary: "Follow by URI",
operationId: "AccountController.follows",
security: [%{"oAuth" => ["follow", "write:follows"]}],
def mutes_operation do
%Operation{
- tags: ["accounts"],
- summary: "Muted accounts",
+ tags: ["Blocks and mutes"],
+ summary: "Retrieve list of mutes",
operationId: "AccountController.mutes",
description: "Accounts the user has muted.",
security: [%{"oAuth" => ["follow", "read:mutes"]}],
def blocks_operation do
%Operation{
- tags: ["accounts"],
- summary: "Blocked users",
+ tags: ["Blocks and mutes"],
+ summary: "Retrieve list of blocks",
operationId: "AccountController.blocks",
description: "View your blocks. See also accounts/:id/{block,unblock}",
security: [%{"oAuth" => ["read:blocks"]}],
def endorsements_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Endorsements",
operationId: "AccountController.endorsements",
description: "Not implemented",
def identity_proofs_operation do
%Operation{
- tags: ["accounts"],
+ tags: ["Retrieve account information"],
summary: "Identity proofs",
operationId: "AccountController.identity_proofs",
# Validators complains about unused path params otherwise
def delete_message_operation do
%Operation{
- tags: ["admin", "chat"],
+ tags: ["Chat administration"],
summary: "Delete an individual chat message",
operationId: "AdminAPI.ChatController.delete_message",
parameters: [
def messages_operation do
%Operation{
- tags: ["admin", "chat"],
- summary: "Get the most recent messages of the chat",
+ tags: ["Chat administration"],
+ summary: "Get chat's messages",
operationId: "AdminAPI.ChatController.messages",
parameters:
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
def show_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chat administration"],
summary: "Create a chat",
operationId: "AdminAPI.ChatController.show",
parameters: [
def show_operation do
%Operation{
- tags: ["Admin", "Config"],
- summary: "Get list of merged default settings with saved in database",
+ tags: ["Instance configuration"],
+ summary: "Retrieve instance configuration",
operationId: "AdminAPI.ConfigController.show",
parameters: [
Operation.parameter(
def update_operation do
%Operation{
- tags: ["Admin", "Config"],
- summary: "Update config settings",
+ tags: ["Instance configuration"],
+ summary: "Update instance configuration",
operationId: "AdminAPI.ConfigController.update",
security: [%{"oAuth" => ["write"]}],
parameters: admin_api_params(),
def descriptions_operation do
%Operation{
- tags: ["Admin", "Config"],
- summary: "Get JSON with config descriptions.",
+ tags: ["Instance configuration"],
+ summary: "Retrieve config description",
operationId: "AdminAPI.ConfigController.descriptions",
security: [%{"oAuth" => ["read"]}],
parameters: admin_api_params(),
def index_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Get a list of available frontends",
+ tags: ["Frontend managment"],
+ summary: "Retrieve a list of available frontends",
operationId: "AdminAPI.FrontendController.index",
security: [%{"oAuth" => ["read"]}],
responses: %{
def install_operation do
%Operation{
- tags: ["Admin", "Reports"],
+ tags: ["Frontend managment"],
summary: "Install a frontend",
operationId: "AdminAPI.FrontendController.install",
security: [%{"oAuth" => ["read"]}],
def show_operation do
%Operation{
- tags: ["Admin", "InstanceDocument"],
- summary: "Get the instance document",
+ tags: ["Instance documents"],
+ summary: "Retrieve an instance document",
operationId: "AdminAPI.InstanceDocumentController.show",
security: [%{"oAuth" => ["read"]}],
parameters: [
def update_operation do
%Operation{
- tags: ["Admin", "InstanceDocument"],
- summary: "Update the instance document",
+ tags: ["Instance documents"],
+ summary: "Update an instance document",
operationId: "AdminAPI.InstanceDocumentController.update",
security: [%{"oAuth" => ["write"]}],
requestBody: Helpers.request_body("Parameters", update_request()),
def delete_operation do
%Operation{
- tags: ["Admin", "InstanceDocument"],
- summary: "Get the instance document",
+ tags: ["Instance documents"],
+ summary: "Delete an instance document",
operationId: "AdminAPI.InstanceDocumentController.delete",
security: [%{"oAuth" => ["write"]}],
parameters: [
def index_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Get a list of generated invites",
operationId: "AdminAPI.InviteController.index",
security: [%{"oAuth" => ["read:invites"]}],
def create_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Create an account registration invite token",
operationId: "AdminAPI.InviteController.create",
security: [%{"oAuth" => ["write:invites"]}],
def revoke_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Revoke invite by token",
operationId: "AdminAPI.InviteController.revoke",
security: [%{"oAuth" => ["write:invites"]}],
def email_operation do
%Operation{
- tags: ["Admin", "Invites"],
+ tags: ["Invites"],
summary: "Sends registration invite via email",
operationId: "AdminAPI.InviteController.email",
security: [%{"oAuth" => ["write:invites"]}],
def index_operation do
%Operation{
- tags: ["Admin", "MediaProxyCache"],
- summary: "Fetch a paginated list of all banned MediaProxy URLs in Cachex",
+ tags: ["MediaProxy cache"],
+ summary: "Retrieve a list of banned MediaProxy URLs",
operationId: "AdminAPI.MediaProxyCacheController.index",
security: [%{"oAuth" => ["read:media_proxy_caches"]}],
parameters: [
responses: %{
200 =>
Operation.response(
- "Array of banned MediaProxy URLs in Cachex",
+ "Array of MediaProxy URLs",
"application/json",
%Schema{
type: :object,
def delete_operation do
%Operation{
- tags: ["Admin", "MediaProxyCache"],
- summary: "Remove a banned MediaProxy URL from Cachex",
+ tags: ["MediaProxy cache"],
+ summary: "Remove a banned MediaProxy URL",
operationId: "AdminAPI.MediaProxyCacheController.delete",
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
parameters: admin_api_params(),
def purge_operation do
%Operation{
- tags: ["Admin", "MediaProxyCache"],
- summary: "Purge and optionally ban a MediaProxy URL",
+ tags: ["MediaProxy cache"],
+ summary: "Purge a URL from MediaProxy cache and optionally ban it",
operationId: "AdminAPI.MediaProxyCacheController.purge",
security: [%{"oAuth" => ["write:media_proxy_caches"]}],
parameters: admin_api_params(),
def index_operation do
%Operation{
- summary: "List OAuth apps",
- tags: ["Admin", "oAuth Apps"],
+ summary: "Retrieve a list of OAuth applications",
+ tags: ["OAuth application managment"],
operationId: "AdminAPI.OAuthAppController.index",
security: [%{"oAuth" => ["write"]}],
parameters: [
def create_operation do
%Operation{
- tags: ["Admin", "oAuth Apps"],
- summary: "Create OAuth App",
+ tags: ["OAuth application managment"],
+ summary: "Create an OAuth application",
operationId: "AdminAPI.OAuthAppController.create",
requestBody: request_body("Parameters", create_request()),
parameters: admin_api_params(),
def update_operation do
%Operation{
- tags: ["Admin", "oAuth Apps"],
- summary: "Update OAuth App",
+ tags: ["OAuth application managment"],
+ summary: "Update OAuth application",
operationId: "AdminAPI.OAuthAppController.update",
parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}],
def delete_operation do
%Operation{
- tags: ["Admin", "oAuth Apps"],
- summary: "Delete OAuth App",
+ tags: ["OAuth application managment"],
+ summary: "Delete OAuth application",
operationId: "AdminAPI.OAuthAppController.delete",
parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write"]}],
def index_operation do
%Operation{
- tags: ["Admin", "Relays"],
- summary: "List Relays",
+ tags: ["Relays"],
+ summary: "Retrieve a list of relays",
operationId: "AdminAPI.RelayController.index",
security: [%{"oAuth" => ["read"]}],
parameters: admin_api_params(),
def follow_operation do
%Operation{
- tags: ["Admin", "Relays"],
- summary: "Follow a Relay",
+ tags: ["Relays"],
+ summary: "Follow a relay",
operationId: "AdminAPI.RelayController.follow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
def unfollow_operation do
%Operation{
- tags: ["Admin", "Relays"],
- summary: "Unfollow a Relay",
+ tags: ["Relays"],
+ summary: "Unfollow a relay",
operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
def index_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Get a list of reports",
+ tags: ["Report managment"],
+ summary: "Retrieve a list of reports",
operationId: "AdminAPI.ReportController.index",
security: [%{"oAuth" => ["read:reports"]}],
parameters: [
def show_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Get an individual report",
+ tags: ["Report managment"],
+ summary: "Retrieve a report",
operationId: "AdminAPI.ReportController.show",
parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:reports"]}],
def update_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Change the state of one or multiple reports",
+ tags: ["Report managment"],
+ summary: "Change state of specified reports",
operationId: "AdminAPI.ReportController.update",
security: [%{"oAuth" => ["write:reports"]}],
parameters: admin_api_params(),
def notes_create_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Create report note",
+ tags: ["Report managment"],
+ summary: "Add a note to the report",
operationId: "AdminAPI.ReportController.notes_create",
parameters: [id_param() | admin_api_params()],
requestBody:
def notes_delete_operation do
%Operation{
- tags: ["Admin", "Reports"],
- summary: "Delete report note",
+ tags: ["Report managment"],
+ summary: "Delete note attached to the report",
operationId: "AdminAPI.ReportController.notes_delete",
parameters: [
Operation.parameter(:report_id, :path, :string, "Report ID"),
def index_operation do
%Operation{
- tags: ["Admin", "Statuses"],
+ tags: ["Status administration"],
operationId: "AdminAPI.StatusController.index",
+ summary: "Get all statuses",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(
def show_operation do
%Operation{
- tags: ["Admin", "Statuses"],
- summary: "Show Status",
+ tags: ["Status adminitration)"],
+ summary: "Get status",
operationId: "AdminAPI.StatusController.show",
parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["read:statuses"]}],
def update_operation do
%Operation{
- tags: ["Admin", "Statuses"],
- summary: "Change the scope of an individual reported status",
+ tags: ["Status adminitration)"],
+ summary: "Change the scope of a status",
operationId: "AdminAPI.StatusController.update",
parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}],
def delete_operation do
%Operation{
- tags: ["Admin", "Statuses"],
- summary: "Delete an individual reported status",
+ tags: ["Status adminitration)"],
+ summary: "Delete status",
operationId: "AdminAPI.StatusController.delete",
parameters: [id_param() | admin_api_params()],
security: [%{"oAuth" => ["write:statuses"]}],
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Applications"],
summary: "Create an application",
description: "Create a new application to obtain OAuth2 credentials",
operationId: "AppController.create",
def verify_credentials_operation do
%Operation{
- tags: ["apps"],
- summary: "Verify your app works",
+ tags: ["Applications"],
+ summary: "Verify the application works",
description: "Confirm that the app's OAuth2 credentials work.",
operationId: "AppController.verify_credentials",
security: [%{"oAuth" => ["read"]}],
def mark_as_read_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chats"],
summary: "Mark all messages in the chat as read",
operationId: "ChatController.mark_as_read",
parameters: [Operation.parameter(:id, :path, :string, "The ID of the Chat")],
def mark_message_as_read_operation do
%Operation{
- tags: ["chat"],
- summary: "Mark one message in the chat as read",
+ tags: ["Chats"],
+ summary: "Mark a message as read",
operationId: "ChatController.mark_message_as_read",
parameters: [
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
def show_operation do
%Operation{
- tags: ["chat"],
- summary: "Create a chat",
+ tags: ["Chats"],
+ summary: "Retrieve a chat",
operationId: "ChatController.show",
parameters: [
Operation.parameter(
def create_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chats"],
summary: "Create a chat",
operationId: "ChatController.create",
parameters: [
def index_operation do
%Operation{
- tags: ["chat"],
- summary: "Get a list of chats that you participated in",
+ tags: ["Chats"],
+ summary: "Retrieve list of chats",
operationId: "ChatController.index",
parameters: [
Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
def messages_operation do
%Operation{
- tags: ["chat"],
- summary: "Get the most recent messages of the chat",
+ tags: ["Chats"],
+ summary: "Retrieve chat's messages",
operationId: "ChatController.messages",
parameters:
[Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
def post_chat_message_operation do
%Operation{
- tags: ["chat"],
+ tags: ["Chats"],
summary: "Post a message to the chat",
operationId: "ChatController.post_chat_message",
parameters: [
def delete_message_operation do
%Operation{
- tags: ["chat"],
- summary: "delete_message",
+ tags: ["Chats"],
+ summary: "Delete message",
operationId: "ChatController.delete_message",
parameters: [
Operation.parameter(:id, :path, :string, "The ID of the Chat"),
def index_operation do
%Operation{
tags: ["Conversations"],
- summary: "Show conversation",
+ summary: "List of conversations",
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "ConversationController.index",
parameters: [
def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
- summary: "Mark as read",
+ summary: "Mark conversation as read",
operationId: "ConversationController.mark_as_read",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
def index_operation do
%Operation{
- tags: ["custom_emojis"],
- summary: "List custom custom emojis",
+ tags: ["Custom emojis"],
+ summary: "Retrieve a list of custom emojis",
description: "Returns custom emojis that are available on the server.",
operationId: "CustomEmojiController.index",
responses: %{
def index_operation do
%Operation{
- tags: ["domain_blocks"],
- summary: "Fetch domain blocks",
- description: "View domains the user has blocked.",
+ tags: ["Domain blocks"],
+ summary: "Retrieve a list of blocked domains",
security: [%{"oAuth" => ["follow", "read:blocks"]}],
operationId: "DomainBlockController.index",
responses: %{
# Supporting domain query parameter is deprecated in Mastodon API
def create_operation do
%Operation{
- tags: ["domain_blocks"],
+ tags: ["Domain blocks"],
summary: "Block a domain",
description: """
Block a domain to:
# Supporting domain query parameter is deprecated in Mastodon API
def delete_operation do
%Operation{
- tags: ["domain_blocks"],
+ tags: ["Domain blocks"],
summary: "Unblock a domain",
description: "Remove a domain block, if it exists in the user's array of blocked domains.",
operationId: "DomainBlockController.delete",
def index_operation do
%Operation{
- tags: ["Emoji Reactions"],
+ tags: ["Emoji reactions"],
summary:
"Get an object of emoji to account mappings with accounts that reacted to the post",
parameters: [
def create_operation do
%Operation{
- tags: ["Emoji Reactions"],
+ tags: ["Emoji reactions"],
summary: "React to a post with a unicode emoji",
parameters: [
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
def delete_operation do
%Operation{
- tags: ["Emoji Reactions"],
+ tags: ["Emoji reactions"],
summary: "Remove a reaction to a post with a unicode emoji",
parameters: [
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
end
defp array_of_reactions_response do
- Operation.response("Array of Emoji Reactions", "application/json", %Schema{
+ Operation.response("Array of Emoji reactions", "application/json", %Schema{
type: :array,
items: emoji_reaction(),
example: [emoji_reaction().example]
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
def open_api_operation(action) do
def index_operation do
%Operation{
- tags: ["apps"],
- summary: "View all filters",
+ tags: ["Filters"],
+ summary: "All filters",
operationId: "FilterController.index",
security: [%{"oAuth" => ["read:filters"]}],
responses: %{
- 200 => Operation.response("Filters", "application/json", array_of_filters())
+ 200 => Operation.response("Filters", "application/json", array_of_filters()),
+ 403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def create_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Filters"],
summary: "Create a filter",
operationId: "FilterController.create",
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
security: [%{"oAuth" => ["write:filters"]}],
- responses: %{200 => Operation.response("Filter", "application/json", filter())}
+ responses: %{
+ 200 => Operation.response("Filter", "application/json", filter()),
+ 403 => Operation.response("Error", "application/json", ApiError)
+ }
}
end
def show_operation do
%Operation{
- tags: ["apps"],
- summary: "View all filters",
+ tags: ["Filters"],
+ summary: "Filter",
parameters: [id_param()],
operationId: "FilterController.show",
security: [%{"oAuth" => ["read:filters"]}],
responses: %{
- 200 => Operation.response("Filter", "application/json", filter())
+ 200 => Operation.response("Filter", "application/json", filter()),
+ 403 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Filters"],
summary: "Update a filter",
parameters: [id_param()],
operationId: "FilterController.update",
requestBody: Helpers.request_body("Parameters", update_request(), required: true),
security: [%{"oAuth" => ["write:filters"]}],
responses: %{
- 200 => Operation.response("Filter", "application/json", filter())
+ 200 => Operation.response("Filter", "application/json", filter()),
+ 403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
- tags: ["apps"],
+ tags: ["Filters"],
summary: "Remove a filter",
parameters: [id_param()],
operationId: "FilterController.delete",
Operation.response("Filter", "application/json", %Schema{
type: :object,
description: "Empty object"
- })
+ }),
+ 403 => Operation.response("Error", "application/json", ApiError)
}
}
end
nullable: true,
description: "Consider word boundaries?",
default: true
+ },
+ expires_in: %Schema{
+ nullable: true,
+ type: :integer,
+ description:
+ "Number of seconds from now the filter should expire. Otherwise, null for a filter that doesn't expire."
}
- # TODO: probably should implement filter expiration
- # expires_in: %Schema{
- # type: :string,
- # format: :"date-time",
- # description:
- # "ISO 8601 Datetime for when the filter expires. Otherwise,
- # null for a filter that doesn't expire."
- # }
},
required: [:phrase, :context],
example: %{
def index_operation do
%Operation{
- tags: ["Follow Requests"],
- summary: "Pending Follows",
+ tags: ["Follow requests"],
+ summary: "Retrieve follow requests",
security: [%{"oAuth" => ["read:follows", "follow"]}],
operationId: "FollowRequestController.index",
responses: %{
def authorize_operation do
%Operation{
- tags: ["Follow Requests"],
- summary: "Accept Follow",
+ tags: ["Follow requests"],
+ summary: "Accept follow request",
operationId: "FollowRequestController.authorize",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
def reject_operation do
%Operation{
- tags: ["Follow Requests"],
- summary: "Reject Follow",
+ tags: ["Follow requests"],
+ summary: "Reject follow request",
operationId: "FollowRequestController.reject",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
def show_operation do
%Operation{
tags: ["Instance"],
- summary: "Fetch instance",
+ summary: "Retrieve instance information",
description: "Information about the server",
operationId: "InstanceController.show",
responses: %{
def peers_operation do
%Operation{
tags: ["Instance"],
- summary: "List of known hosts",
+ summary: "Retrieve list of known instances",
operationId: "InstanceController.peers",
responses: %{
200 => Operation.response("Array of domains", "application/json", array_of_domains())
def index_operation do
%Operation{
tags: ["Lists"],
- summary: "Show user's lists",
+ summary: "Retrieve a list of lists",
description: "Fetch all lists that the user owns",
security: [%{"oAuth" => ["read:lists"]}],
operationId: "ListController.index",
def create_operation do
%Operation{
tags: ["Lists"],
- summary: "Create a list",
+ summary: "Create a list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.create",
requestBody: create_update_request(),
def show_operation do
%Operation{
tags: ["Lists"],
- summary: "Show a single list",
+ summary: "Retrieve a list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.show",
parameters: [id_param()],
def list_accounts_operation do
%Operation{
tags: ["Lists"],
- summary: "View accounts in list",
+ summary: "Retrieve accounts in list",
operationId: "ListController.list_accounts",
parameters: [id_param()],
security: [%{"oAuth" => ["read:lists"]}],
def create_operation do
%Operation{
- tags: ["media"],
+ tags: ["Media attachments"],
summary: "Upload media as attachment",
description: "Creates an attachment to be used with a new status.",
operationId: "MediaController.create",
def update_operation do
%Operation{
- tags: ["media"],
- summary: "Upload media as attachment",
+ tags: ["Media attachments"],
+ summary: "Update attachment",
description: "Creates an attachment to be used with a new status.",
operationId: "MediaController.update",
security: [%{"oAuth" => ["write:media"]}],
def show_operation do
%Operation{
- tags: ["media"],
- summary: "Show Uploaded media attachment",
+ tags: ["Media attachments"],
+ summary: "Attachment",
operationId: "MediaController.show",
parameters: [id_param()],
security: [%{"oAuth" => ["read:media"]}],
def create2_operation do
%Operation{
- tags: ["media"],
- summary: "Upload media as attachment",
+ tags: ["Media attachments"],
+ summary: "Upload media as attachment (v2)",
description: "Creates an attachment to be used with a new status.",
operationId: "MediaController.create2",
security: [%{"oAuth" => ["write:media"]}],
def index_operation do
%Operation{
tags: ["Notifications"],
- summary: "Get all notifications",
+ summary: "Retrieve a list of notifications",
description:
"Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.",
operationId: "NotificationController.index",
def show_operation do
%Operation{
tags: ["Notifications"],
- summary: "Get a single notification",
+ summary: "Retrieve a notification",
description: "View information about a notification with a given ID.",
operationId: "NotificationController.show",
security: [%{"oAuth" => ["read:notifications"]}],
def dismiss_operation do
%Operation{
tags: ["Notifications"],
- summary: "Dismiss a single notification",
+ summary: "Dismiss a notification",
description: "Clear a single notification from the server.",
operationId: "NotificationController.dismiss",
parameters: [id_param()],
def confirmation_resend_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Resend confirmation email. Expects `email` or `nickname`",
+ tags: ["Account credentials"],
+ summary: "Resend confirmation email",
+ description: "Expects `email` or `nickname`.",
operationId: "PleromaAPI.AccountController.confirmation_resend",
parameters: [
Operation.parameter(:email, :query, :string, "Email of that needs to be verified",
def favourites_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Returns favorites timeline of any user",
+ tags: ["Retrieve account information"],
+ summary: "Favorites",
+ description:
+ "Only returns data if the user has opted into sharing it. See `hide_favorites` in [Update account credentials](#operation/AccountController.update_credentials).",
operationId: "PleromaAPI.AccountController.favourites",
parameters: [id_param() | pagination_params()],
security: [%{"oAuth" => ["read:favourites"]}],
def subscribe_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Subscribe to receive notifications for all statuses posted by a user",
+ tags: ["Account actions"],
+ summary: "Subscribe",
+ description: "Receive notifications for all statuses posted by the account.",
operationId: "PleromaAPI.AccountController.subscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
def unsubscribe_operation do
%Operation{
- tags: ["Accounts"],
- summary: "Unsubscribe to stop receiving notifications from user statuses",
+ tags: ["Account actions"],
+ summary: "Unsubscribe",
+ description: "Stop receiving notifications for all statuses posted by the account.",
operationId: "PleromaAPI.AccountController.unsubscribe",
parameters: [id_param()],
security: [%{"oAuth" => ["follow", "write:follows"]}],
def show_operation do
%Operation{
tags: ["Conversations"],
- summary: "The conversation with the given ID",
+ summary: "Conversation",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
def statuses_operation do
%Operation{
tags: ["Conversations"],
- summary: "Timeline for a given conversation",
+ summary: "Timeline for conversation",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
def update_operation do
%Operation{
tags: ["Conversations"],
- summary: "Update a conversation. Used to change the set of recipients.",
+ summary: "Update conversation",
+ description: "Change set of recipients for the conversation.",
parameters: [
Operation.parameter(:id, :path, :string, "Conversation ID",
example: "123",
def mark_as_read_operation do
%Operation{
tags: ["Conversations"],
- summary: "Marks all user's conversations as read",
+ summary: "Marks all conversations as read",
security: [%{"oAuth" => ["write:conversations"]}],
operationId: "PleromaAPI.ConversationController.mark_as_read",
responses: %{
def create_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Add new file to the pack",
operationId: "PleromaAPI.EmojiPackController.add_file",
security: [%{"oAuth" => ["write"]}],
def update_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Add new file to the pack",
operationId: "PleromaAPI.EmojiPackController.update_file",
security: [%{"oAuth" => ["write"]}],
def delete_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Delete emoji file from pack",
operationId: "PleromaAPI.EmojiPackController.delete_file",
security: [%{"oAuth" => ["write"]}],
def remote_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Make request to another instance for emoji packs list",
security: [%{"oAuth" => ["write"]}],
parameters: [
def index_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Lists local custom emoji packs",
operationId: "PleromaAPI.EmojiPackController.index",
parameters: [
def show_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Show emoji pack",
operationId: "PleromaAPI.EmojiPackController.show",
parameters: [
def archive_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Requests a local pack archive from the instance",
operationId: "PleromaAPI.EmojiPackController.archive",
parameters: [name_param()],
def download_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Download pack from another instance",
operationId: "PleromaAPI.EmojiPackController.download",
security: [%{"oAuth" => ["write"]}],
def create_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Create an empty pack",
operationId: "PleromaAPI.EmojiPackController.create",
security: [%{"oAuth" => ["write"]}],
def delete_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Delete a custom emoji pack",
operationId: "PleromaAPI.EmojiPackController.delete",
security: [%{"oAuth" => ["write"]}],
def update_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Updates (replaces) pack metadata",
operationId: "PleromaAPI.EmojiPackController.update",
security: [%{"oAuth" => ["write"]}],
def import_from_filesystem_operation do
%Operation{
- tags: ["Emoji Packs"],
+ tags: ["Emoji packs"],
summary: "Imports packs from filesystem",
operationId: "PleromaAPI.EmojiPackController.import",
security: [%{"oAuth" => ["write"]}],
def show_operation do
%Operation{
- tags: ["PleromaInstances"],
- summary: "Instances federation status",
+ tags: ["Instance"],
+ summary: "Retrieve federation status",
description: "Information about instances deemed unreachable by the server",
operationId: "PleromaInstances.show",
responses: %{
def show_operation do
%Operation{
tags: ["Mascot"],
- summary: "Gets user mascot image",
+ summary: "Retrieve mascot",
security: [%{"oAuth" => ["read:accounts"]}],
operationId: "PleromaAPI.MascotController.show",
responses: %{
def update_operation do
%Operation{
tags: ["Mascot"],
- summary: "Set/clear user avatar image",
+ summary: "Set or clear mascot",
description:
"Behaves exactly the same as `POST /api/v1/upload`. Can only accept images - any attempt to upload non-image files will be met with `HTTP 415 Unsupported Media Type`.",
operationId: "PleromaAPI.MascotController.update",
def mark_as_read_operation do
%Operation{
tags: ["Notifications"],
- summary: "Mark notifications as read. Query parameters are mutually exclusive.",
+ summary: "Mark notifications as read",
+ description: "Query parameters are mutually exclusive.",
requestBody:
request_body("Parameters", %Schema{
type: :object,
responses: %{
200 =>
Operation.response(
- "A Notification or array of Motifications",
+ "A Notification or array of Notifications",
"application/json",
%Schema{
anyOf: [
def create_operation do
%Operation{
- tags: ["reports"],
+ tags: ["Reports"],
summary: "File a report",
description: "Report problematic users to your moderators",
operationId: "ReportController.create",
def index_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "View scheduled statuses",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: pagination_params(),
def show_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "View a single scheduled status",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
def update_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "Schedule a status",
operationId: "ScheduledActivity.update",
security: [%{"oAuth" => ["write:statuses"]}],
def delete_operation do
%Operation{
- tags: ["Scheduled Statuses"],
+ tags: ["Scheduled statuses"],
summary: "Cancel a scheduled status",
security: [%{"oAuth" => ["write:statuses"]}],
parameters: [id_param()],
def index_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Get multiple statuses by IDs",
+ tags: ["Retrieve status information"],
+ summary: "Multiple statuses",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(
def create_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Publish new status",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Post a new status",
def show_operation do
%Operation{
- tags: ["Statuses"],
- summary: "View specific status",
+ tags: ["Retrieve status information"],
+ summary: "Status",
description: "View information about a status",
operationId: "StatusController.show",
security: [%{"oAuth" => ["read:statuses"]}],
def delete_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Delete status",
+ tags: ["Status actions"],
+ summary: "Delete",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Delete one of your own statuses",
operationId: "StatusController.delete",
def reblog_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Boost",
+ tags: ["Status actions"],
+ summary: "Reblog",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Share a status",
operationId: "StatusController.reblog",
def unreblog_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Undo boost",
+ tags: ["Status actions"],
+ summary: "Undo reblog",
security: [%{"oAuth" => ["write:statuses"]}],
description: "Undo a reshare of a status",
operationId: "StatusController.unreblog",
def favourite_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Favourite",
security: [%{"oAuth" => ["write:favourites"]}],
description: "Add a status to your favourites list",
def unfavourite_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Undo favourite",
security: [%{"oAuth" => ["write:favourites"]}],
description: "Remove a status from your favourites list",
def pin_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Pin to profile",
security: [%{"oAuth" => ["write:accounts"]}],
description: "Feature one of your own public statuses at the top of your profile",
def unpin_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Unpin to profile",
+ tags: ["Status actions"],
+ summary: "Unpin from profile",
security: [%{"oAuth" => ["write:accounts"]}],
description: "Unfeature a status from the top of your profile",
operationId: "StatusController.unpin",
def bookmark_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Bookmark",
security: [%{"oAuth" => ["write:bookmarks"]}],
description: "Privately bookmark a status",
def unbookmark_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Undo bookmark",
security: [%{"oAuth" => ["write:bookmarks"]}],
description: "Remove a status from your private bookmarks",
def mute_conversation_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Mute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description: "Do not receive notifications for the thread that this status is part of.",
def unmute_conversation_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Status actions"],
summary: "Unmute conversation",
security: [%{"oAuth" => ["write:mutes"]}],
description:
def card_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Retrieve status information"],
deprecated: true,
summary: "Preview card",
description: "Deprecated in favor of card property inlined on Status entity",
def favourited_by_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Retrieve status information"],
summary: "Favourited by",
description: "View who favourited a given status",
operationId: "StatusController.favourited_by",
def reblogged_by_operation do
%Operation{
- tags: ["Statuses"],
- summary: "Boosted by",
- description: "View who boosted a given status",
+ tags: ["Retrieve status information"],
+ summary: "Reblogged by",
+ description: "View who reblogged a given status",
operationId: "StatusController.reblogged_by",
security: [%{"oAuth" => ["read:accounts"]}],
parameters: [id_param()],
def context_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Retrieve status information"],
summary: "Parent and child statuses",
description: "View statuses above and below this status in the thread",
operationId: "StatusController.context",
def favourites_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Timelines"],
summary: "Favourited statuses",
description:
"Statuses the user has favourited. Please note that you have to use the link headers to paginate this. You can not build the query parameters yourself.",
def bookmarks_operation do
%Operation{
- tags: ["Statuses"],
+ tags: ["Timelines"],
summary: "Bookmarked statuses",
description: "Statuses the user has bookmarked",
operationId: "StatusController.bookmarks",
items: %Schema{type: :string},
description: "Array of Attachment ids to be attached as media."
},
- poll: %Schema{
- nullable: true,
- type: :object,
- required: [:options],
- properties: %{
- options: %Schema{
- type: :array,
- items: %Schema{type: :string},
- description: "Array of possible answers. Must be provided with `poll[expires_in]`."
- },
- expires_in: %Schema{
- type: :integer,
- nullable: true,
- description:
- "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
- },
- multiple: %Schema{
- allOf: [BooleanLike],
- nullable: true,
- description: "Allow multiple choices?"
- },
- hide_totals: %Schema{
- allOf: [BooleanLike],
- nullable: true,
- description: "Hide vote counts until the poll ends?"
- }
- }
- },
+ poll: poll_params(),
in_reply_to_id: %Schema{
nullable: true,
allOf: [FlakeID],
}
end
+ def poll_params do
+ %Schema{
+ nullable: true,
+ type: :object,
+ required: [:options, :expires_in],
+ properties: %{
+ options: %Schema{
+ type: :array,
+ items: %Schema{type: :string},
+ description: "Array of possible answers. Must be provided with `poll[expires_in]`."
+ },
+ expires_in: %Schema{
+ type: :integer,
+ nullable: true,
+ description:
+ "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
+ },
+ multiple: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Allow multiple choices?"
+ },
+ hide_totals: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Hide vote counts until the poll ends?"
+ }
+ }
+ }
+ end
+
def id_param do
Operation.parameter(:id, :path, FlakeID, "Status ID",
example: "9umDrYheeY451cQnEe",
def create_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Subscribe to push notifications",
description:
"Add a Web Push API subscription to receive notifications. Each access token can have one push subscription. If you create a new subscription, the old subscription is deleted.",
security: [%{"oAuth" => ["push"]}],
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{
- 200 => Operation.response("Push Subscription", "application/json", PushSubscription),
+ 200 => Operation.response("Push subscription", "application/json", PushSubscription),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError)
}
def show_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Get current subscription",
description: "View the PushSubscription currently associated with this access token.",
operationId: "SubscriptionController.show",
security: [%{"oAuth" => ["push"]}],
responses: %{
- 200 => Operation.response("Push Subscription", "application/json", PushSubscription),
+ 200 => Operation.response("Push subscription", "application/json", PushSubscription),
403 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
def update_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Change types of notifications",
description:
"Updates the current push subscription. Only the data part can be updated. To change fundamentals, a new subscription must be created instead.",
security: [%{"oAuth" => ["push"]}],
requestBody: Helpers.request_body("Parameters", update_request(), required: true),
responses: %{
- 200 => Operation.response("Push Subscription", "application/json", PushSubscription),
+ 200 => Operation.response("Push subscription", "application/json", PushSubscription),
403 => Operation.response("Error", "application/json", ApiError)
}
}
def delete_operation do
%Operation{
- tags: ["Push Subscriptions"],
+ tags: ["Push subscriptions"],
summary: "Remove current subscription",
description: "Removes the current Web Push API subscription.",
operationId: "SubscriptionController.delete",
tags: ["Timelines"],
summary: "Direct timeline",
description:
- "View statuses with a “direct” privacy, from your account or in your notifications",
- deprecated: true,
+ "View statuses with a “direct” scope addressed to the account. Using this endpoint is discouraged, please use [conversations](#tag/Conversations) or [chats](#tag/Chats).",
parameters: [with_muted_param() | pagination_params()],
security: [%{"oAuth" => ["read:statuses"]}],
operationId: "TimelineController.direct",
def follow_operation do
%Operation{
- tags: ["follow_import"],
- summary: "Imports your follows.",
+ tags: ["Data import"],
+ summary: "Import follows",
operationId: "UserImportController.follow",
requestBody: request_body("Parameters", import_request(), required: true),
responses: %{
def blocks_operation do
%Operation{
- tags: ["blocks_import"],
- summary: "Imports your blocks.",
+ tags: ["Data import"],
+ summary: "Import blocks",
operationId: "UserImportController.blocks",
requestBody: request_body("Parameters", import_request(), required: true),
responses: %{
def mutes_operation do
%Operation{
- tags: ["mutes_import"],
- summary: "Imports your mutes.",
+ tags: ["Data import"],
+ summary: "Import mutes",
operationId: "UserImportController.mutes",
requestBody: request_body("Parameters", import_request(), required: true),
responses: %{
hide_notification_contents: %Schema{type: :boolean}
}
},
- relationship: AccountRelationship,
+ relationship: %Schema{allOf: [AccountRelationship], nullable: true},
settings_store: %Schema{
type: :object,
description:
OpenApiSpex.schema(%{
title: "AccountRelationship",
- description: "Response schema for relationship",
+ description: "Relationship between current account and requested account",
type: :object,
properties: %{
blocked_by: %Schema{type: :boolean},
defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Attachment
- alias Pleroma.Web.ApiSpec.Schemas.Poll
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+ alias Pleroma.Web.ApiSpec.StatusOperation
require OpenApiSpex
spoiler_text: %Schema{type: :string, nullable: true},
visibility: %Schema{allOf: [VisibilityScope], nullable: true},
scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true},
- poll: %Schema{allOf: [Poll], nullable: true},
+ poll: StatusOperation.poll_params(),
in_reply_to_id: %Schema{type: :string, nullable: true}
}
}
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
@doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)
@doc "POST /api/v1/filters"
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
- query = %Filter{
- user_id: user.id,
- phrase: params.phrase,
- context: params.context,
- hide: params.irreversible,
- whole_word: params.whole_word
- # TODO: support `expires_in` parameter (as in Mastodon API)
- }
-
- {:ok, response} = Filter.create(query)
-
- render(conn, "show.json", filter: response)
+ with {:ok, response} <-
+ params
+ |> Map.put(:user_id, user.id)
+ |> Map.put(:hide, params[:irreversible])
+ |> Map.delete(:irreversible)
+ |> Filter.create() do
+ render(conn, "show.json", filter: response)
+ end
end
@doc "GET /api/v1/filters/:id"
def show(%{assigns: %{user: user}} = conn, %{id: filter_id}) do
- filter = Filter.get(filter_id, user)
-
- render(conn, "show.json", filter: filter)
+ with %Filter{} = filter <- Filter.get(filter_id, user) do
+ render(conn, "show.json", filter: filter)
+ else
+ nil -> {:error, :not_found}
+ end
end
@doc "PUT /api/v1/filters/:id"
%{id: filter_id}
) do
params =
- params
- |> Map.delete(:irreversible)
- |> Map.put(:hide, params[:irreversible])
- |> Enum.reject(fn {_key, value} -> is_nil(value) end)
- |> Map.new()
-
- # TODO: support `expires_in` parameter (as in Mastodon API)
+ if is_boolean(params[:irreversible]) do
+ params
+ |> Map.put(:hide, params[:irreversible])
+ |> Map.delete(:irreversible)
+ else
+ params
+ end
with %Filter{} = filter <- Filter.get(filter_id, user),
{:ok, %Filter{} = filter} <- Filter.update(filter, params) do
render(conn, "show.json", filter: filter)
+ else
+ nil -> {:error, :not_found}
+ error -> error
end
end
@doc "DELETE /api/v1/filters/:id"
def delete(%{assigns: %{user: user}} = conn, %{id: filter_id}) do
- query = %Filter{
- user_id: user.id,
- filter_id: filter_id
- }
-
- {:ok, _} = Filter.delete(query)
- json(conn, %{})
+ with %Filter{} = filter <- Filter.get(filter_id, user),
+ {:ok, _} <- Filter.delete(filter) do
+ json(conn, %{})
+ else
+ nil -> {:error, :not_found}
+ error -> error
+ end
end
end
--- /dev/null
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PurgeExpiredFilter do
+ @moduledoc """
+ Worker which purges expired filters
+ """
+
+ use Oban.Worker, queue: :filter_expiration, max_attempts: 1, unique: [period: :infinity]
+
+ import Ecto.Query
+
+ alias Oban.Job
+ alias Pleroma.Repo
+
+ @spec enqueue(%{filter_id: integer(), expires_at: DateTime.t()}) ::
+ {:ok, Job.t()} | {:error, Ecto.Changeset.t()}
+ def enqueue(args) do
+ {scheduled_at, args} = Map.pop(args, :expires_at)
+
+ args
+ |> new(scheduled_at: scheduled_at)
+ |> Oban.insert()
+ end
+
+ @impl true
+ def perform(%Job{args: %{"filter_id" => id}}) do
+ Pleroma.Filter
+ |> Repo.get(id)
+ |> Repo.delete()
+ end
+
+ @spec get_expiration(pos_integer()) :: Job.t() | nil
+ def get_expiration(id) do
+ from(j in Job,
+ where: j.state == "scheduled",
+ where: j.queue == "filter_expiration",
+ where: fragment("?->'filter_id' = ?", j.args, ^id)
+ )
+ |> Repo.one()
+ end
+end
use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
- alias Pleroma.Config
+ alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
- alias Pleroma.Web.CommonAPI
require Logger
@impl Oban.Worker
def perform(%Job{args: %{"activity_id" => activity_id}}) do
- if Config.get([ScheduledActivity, :enabled]) do
- case Pleroma.Repo.get(ScheduledActivity, activity_id) do
- %ScheduledActivity{} = scheduled_activity ->
- post_activity(scheduled_activity)
-
- _ ->
- Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
- end
+ with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id),
+ %User{} = user <- find_user(scheduled_activity.user_id) do
+ params = atomize_keys(scheduled_activity.params)
+
+ Repo.transaction(fn ->
+ {:ok, activity} = Pleroma.Web.CommonAPI.post(user, params)
+ {:ok, _} = ScheduledActivity.delete(scheduled_activity)
+ activity
+ end)
+ else
+ {:error, :scheduled_activity_not_found} = error ->
+ Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
+ error
+
+ {:error, :user_not_found} = error ->
+ Logger.error("#{__MODULE__} Couldn't find user for scheduled activity: #{activity_id}")
+ error
end
end
- defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do
- params = Map.new(params, fn {key, value} -> {String.to_existing_atom(key), value} end)
+ defp find_scheduled_activity(id) do
+ with nil <- Repo.get(ScheduledActivity, id) do
+ {:error, :scheduled_activity_not_found}
+ end
+ end
- with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)},
- {:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)},
- {:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do
- :ok
- else
- error ->
- Logger.error(
- "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
- )
+ defp find_user(id) do
+ with nil <- User.get_cached_by_id(id) do
+ {:error, :user_not_found}
end
end
+
+ defp atomize_keys(map) do
+ Map.new(map, fn
+ {key, value} when is_map(value) -> {String.to_existing_atom(key), atomize_keys(value)}
+ {key, value} -> {String.to_existing_atom(key), value}
+ end)
+ end
end
--- /dev/null
+external_emoji, https://example.com/emoji.png
end
test "check_activity_expiration_config/0" do
- clear_config(Pleroma.ActivityExpiration, enabled: true)
+ clear_config([Pleroma.ActivityExpiration], enabled: true)
assert capture_log(fn ->
DeprecationWarnings.check_activity_expiration_config()
end
test "check_uploders_s3_public_endpoint/0" do
- clear_config(Pleroma.Uploaders.S3, public_endpoint: "https://fake.amazonaws.com/bucket/")
+ clear_config([Pleroma.Uploaders.S3], public_endpoint: "https://fake.amazonaws.com/bucket/")
assert capture_log(fn ->
DeprecationWarnings.check_uploders_s3_public_endpoint()
import Pleroma.Factory
+ alias Oban.Job
alias Pleroma.Filter
- alias Pleroma.Repo
+
+ setup do
+ [user: insert(:user)]
+ end
describe "creating filters" do
- test "creating one filter" do
- user = insert(:user)
+ test "creation validation error", %{user: user} do
+ attrs = %{
+ user_id: user.id,
+ expires_in: 60
+ }
+
+ {:error, _} = Filter.create(attrs)
+
+ assert Repo.all(Job) == []
+ end
- query = %Filter{
+ test "use passed expires_at instead expires_in", %{user: user} do
+ now = NaiveDateTime.utc_now()
+
+ attrs = %{
user_id: user.id,
- filter_id: 42,
+ expires_at: now,
phrase: "knights",
- context: ["home"]
+ context: ["home"],
+ expires_in: 600
}
- {:ok, %Filter{} = filter} = Filter.create(query)
+ {:ok, %Filter{} = filter} = Filter.create(attrs)
+
result = Filter.get(filter.filter_id, user)
- assert query.phrase == result.phrase
- end
+ assert result.expires_at == NaiveDateTime.truncate(now, :second)
- test "creating one filter without a pre-defined filter_id" do
- user = insert(:user)
+ [job] = Repo.all(Job)
- query = %Filter{
+ assert DateTime.truncate(job.scheduled_at, :second) ==
+ now |> NaiveDateTime.truncate(:second) |> DateTime.from_naive!("Etc/UTC")
+ end
+
+ test "creating one filter", %{user: user} do
+ attrs = %{
user_id: user.id,
+ filter_id: 42,
phrase: "knights",
context: ["home"]
}
- {:ok, %Filter{} = filter} = Filter.create(query)
- # Should start at 1
- assert filter.filter_id == 1
+ {:ok, %Filter{} = filter} = Filter.create(attrs)
+ result = Filter.get(filter.filter_id, user)
+ assert attrs.phrase == result.phrase
end
- test "creating additional filters uses previous highest filter_id + 1" do
- user = insert(:user)
-
- query_one = %Filter{
+ test "creating with expired_at", %{user: user} do
+ attrs = %{
user_id: user.id,
filter_id: 42,
phrase: "knights",
+ context: ["home"],
+ expires_in: 60
+ }
+
+ {:ok, %Filter{} = filter} = Filter.create(attrs)
+ result = Filter.get(filter.filter_id, user)
+ assert attrs.phrase == result.phrase
+
+ assert [_] = Repo.all(Job)
+ end
+
+ test "creating one filter without a pre-defined filter_id", %{user: user} do
+ attrs = %{
+ user_id: user.id,
+ phrase: "knights",
context: ["home"]
}
- {:ok, %Filter{} = filter_one} = Filter.create(query_one)
+ {:ok, %Filter{} = filter} = Filter.create(attrs)
+ # Should start at 1
+ assert filter.filter_id == 1
+ end
+
+ test "creating additional filters uses previous highest filter_id + 1", %{user: user} do
+ filter1 = insert(:filter, user: user)
- query_two = %Filter{
+ attrs = %{
user_id: user.id,
# No filter_id
phrase: "who",
context: ["home"]
}
- {:ok, %Filter{} = filter_two} = Filter.create(query_two)
- assert filter_two.filter_id == filter_one.filter_id + 1
+ {:ok, %Filter{} = filter2} = Filter.create(attrs)
+ assert filter2.filter_id == filter1.filter_id + 1
end
- test "filter_id is unique per user" do
- user_one = insert(:user)
+ test "filter_id is unique per user", %{user: user_one} do
user_two = insert(:user)
- query_one = %Filter{
+ attrs1 = %{
user_id: user_one.id,
phrase: "knights",
context: ["home"]
}
- {:ok, %Filter{} = filter_one} = Filter.create(query_one)
+ {:ok, %Filter{} = filter_one} = Filter.create(attrs1)
- query_two = %Filter{
+ attrs2 = %{
user_id: user_two.id,
phrase: "who",
context: ["home"]
}
- {:ok, %Filter{} = filter_two} = Filter.create(query_two)
+ {:ok, %Filter{} = filter_two} = Filter.create(attrs2)
assert filter_one.filter_id == 1
assert filter_two.filter_id == 1
end
end
- test "deleting a filter" do
- user = insert(:user)
+ test "deleting a filter", %{user: user} do
+ filter = insert(:filter, user: user)
- query = %Filter{
- user_id: user.id,
- filter_id: 0,
- phrase: "knights",
- context: ["home"]
- }
-
- {:ok, _filter} = Filter.create(query)
- {:ok, filter} = Filter.delete(query)
- assert is_nil(Repo.get(Filter, filter.filter_id))
+ assert Repo.get(Filter, filter.id)
+ {:ok, filter} = Filter.delete(filter)
+ refute Repo.get(Filter, filter.id)
end
- test "getting all filters by an user" do
- user = insert(:user)
-
- query_one = %Filter{
+ test "deleting a filter with expires_at is removing Oban job too", %{user: user} do
+ attrs = %{
user_id: user.id,
- filter_id: 1,
- phrase: "knights",
- context: ["home"]
+ phrase: "cofe",
+ context: ["home"],
+ expires_in: 600
}
- query_two = %Filter{
- user_id: user.id,
- filter_id: 2,
- phrase: "who",
- context: ["home"]
- }
+ {:ok, filter} = Filter.create(attrs)
+ assert %Job{id: job_id} = Pleroma.Workers.PurgeExpiredFilter.get_expiration(filter.id)
+ {:ok, _} = Filter.delete(filter)
- {:ok, filter_one} = Filter.create(query_one)
- {:ok, filter_two} = Filter.create(query_two)
- filters = Filter.get_filters(user)
- assert filter_one in filters
- assert filter_two in filters
+ assert Repo.get(Job, job_id) == nil
end
- test "updating a filter" do
- user = insert(:user)
+ test "getting all filters by an user", %{user: user} do
+ filter1 = insert(:filter, user: user)
+ filter2 = insert(:filter, user: user)
- query_one = %Filter{
- user_id: user.id,
- filter_id: 1,
- phrase: "knights",
- context: ["home"]
- }
+ filter_ids = user |> Filter.get_filters() |> collect_ids()
+
+ assert filter1.id in filter_ids
+ assert filter2.id in filter_ids
+ end
+
+ test "updating a filter", %{user: user} do
+ filter = insert(:filter, user: user)
changes = %{
phrase: "who",
context: ["home", "timeline"]
}
- {:ok, filter_one} = Filter.create(query_one)
- {:ok, filter_two} = Filter.update(filter_one, changes)
+ {:ok, updated_filter} = Filter.update(filter, changes)
+
+ assert filter != updated_filter
+ assert updated_filter.phrase == changes.phrase
+ assert updated_filter.context == changes.context
+ end
+
+ test "updating with error", %{user: user} do
+ filter = insert(:filter, user: user)
+
+ changes = %{
+ phrase: nil
+ }
- assert filter_one != filter_two
- assert filter_two.phrase == changes.phrase
- assert filter_two.context == changes.context
+ {:error, _} = Filter.update(filter, changes)
end
end
setup do
user = insert(:user)
- date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
- date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
- date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
-
- insert(:note_activity, user: user, published: date1)
- insert(:note_activity, user: user, published: date2)
- insert(:note_activity, user: user, published: date3)
+ insert(:note_activity, user: user)
+ insert(:note_activity, user: user)
+ insert(:note_activity, user: user)
%{user: user}
end
test "renders user's statuses", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
- assert json_response(conn, 200) |> length() == 3
+ assert %{"total" => 3, "activities" => activities} = json_response(conn, 200)
+ assert length(activities) == 3
end
test "renders user's statuses with pagination", %{conn: conn, user: user} do
- conn1 = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1")
-
- response1 = json_response(conn1, 200)
-
- assert response1 |> length() == 1
-
- conn2 = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2")
-
- response2 = json_response(conn2, 200)
+ %{"total" => 3, "activities" => [activity1]} =
+ conn
+ |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1")
+ |> json_response(200)
- assert response2 |> length() == 1
+ %{"total" => 3, "activities" => [activity2]} =
+ conn
+ |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2")
+ |> json_response(200)
- refute response1 == response2
+ refute activity1 == activity2
end
test "doesn't return private statuses by default", %{conn: conn, user: user} do
{:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
+ %{"total" => 4, "activities" => activities} =
+ conn
+ |> get("/api/pleroma/admin/users/#{user.nickname}/statuses")
+ |> json_response(200)
- assert json_response(conn, 200) |> length() == 4
+ assert length(activities) == 4
end
test "returns private statuses with godmode on", %{conn: conn, user: user} do
{:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
+ %{"total" => 5, "activities" => activities} =
+ conn
+ |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
+ |> json_response(200)
- assert json_response(conn, 200) |> length() == 5
+ assert length(activities) == 5
end
test "excludes reblogs by default", %{conn: conn, user: user} do
{:ok, activity} = CommonAPI.post(user, %{status: "."})
{:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
- conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
- assert json_response(conn_res, 200) |> length() == 0
-
- conn_res =
- get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
+ assert %{"total" => 0, "activities" => []} ==
+ conn
+ |> get("/api/pleroma/admin/users/#{other_user.nickname}/statuses")
+ |> json_response(200)
- assert json_response(conn_res, 200) |> length() == 1
+ assert %{"total" => 1, "activities" => [_]} =
+ conn
+ |> get(
+ "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true"
+ )
+ |> json_response(200)
end
end
insert_pair(:note_activity, user: user)
activity = insert(:note_activity, user: user2)
- ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
+ %{"total" => 2, "activities" => activities} =
+ conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200)
- response = json_response(ret_conn, 200)
+ assert length(activities) == 2
- assert length(response) == 2
+ %{"total" => 1, "activities" => [_]} =
+ conn |> get("/api/pleroma/admin/instances/test.com/statuses") |> json_response(200)
- ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses")
+ %{"total" => 0, "activities" => []} =
+ conn |> get("/api/pleroma/admin/instances/nonexistent.com/statuses") |> json_response(200)
- response = json_response(ret_conn, 200)
-
- assert length(response) == 1
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses")
+ CommonAPI.repeat(activity.id, user)
- response = json_response(ret_conn, 200)
+ %{"total" => 2, "activities" => activities} =
+ conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(200)
- assert Enum.empty?(response)
+ assert length(activities) == 2
- CommonAPI.repeat(activity.id, user)
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
- response = json_response(ret_conn, 200)
- assert length(response) == 2
+ %{"total" => 3, "activities" => activities} =
+ conn
+ |> get("/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
+ |> json_response(200)
- ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
- response = json_response(ret_conn, 200)
- assert length(response) == 3
+ assert length(activities) == 3
end
end
defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do
use Pleroma.Web.ConnCase, async: true
+ use Oban.Testing, repo: Pleroma.Repo
- alias Pleroma.Web.MastodonAPI.FilterView
+ import Pleroma.Factory
- test "creating a filter" do
- %{conn: conn} = oauth_access(["write:filters"])
+ alias Pleroma.Filter
+ alias Pleroma.Repo
+ alias Pleroma.Workers.PurgeExpiredFilter
- filter = %Pleroma.Filter{
- phrase: "knights",
- context: ["home"]
- }
-
- conn =
+ test "non authenticated creation request", %{conn: conn} do
+ response =
conn
|> put_req_header("content-type", "application/json")
- |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
-
- assert response = json_response_and_validate_schema(conn, 200)
- assert response["phrase"] == filter.phrase
- assert response["context"] == filter.context
- assert response["irreversible"] == false
- assert response["id"] != nil
- assert response["id"] != ""
+ |> post("/api/v1/filters", %{"phrase" => "knights", context: ["home"]})
+ |> json_response(403)
+
+ assert response["error"] == "Invalid credentials."
+ end
+
+ describe "creating" do
+ setup do: oauth_access(["write:filters"])
+
+ test "a common filter", %{conn: conn, user: user} do
+ params = %{
+ phrase: "knights",
+ context: ["home"],
+ irreversible: true
+ }
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/filters", params)
+ |> json_response_and_validate_schema(200)
+
+ assert response["phrase"] == params.phrase
+ assert response["context"] == params.context
+ assert response["irreversible"] == true
+ assert response["id"] != nil
+ assert response["id"] != ""
+ assert response["expires_at"] == nil
+
+ filter = Filter.get(response["id"], user)
+ assert filter.hide == true
+ end
+
+ test "a filter with expires_in", %{conn: conn, user: user} do
+ in_seconds = 600
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/filters", %{
+ "phrase" => "knights",
+ context: ["home"],
+ expires_in: in_seconds
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert response["irreversible"] == false
+
+ expires_at =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(in_seconds)
+ |> Pleroma.Web.CommonAPI.Utils.to_masto_date()
+
+ assert response["expires_at"] == expires_at
+
+ filter = Filter.get(response["id"], user)
+
+ id = filter.id
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ assert {:ok, %{id: ^id}} =
+ perform_job(PurgeExpiredFilter, %{
+ filter_id: filter.id
+ })
+
+ assert Repo.aggregate(Filter, :count, :id) == 0
+ end
end
test "fetching a list of filters" do
%{user: user, conn: conn} = oauth_access(["read:filters"])
- query_one = %Pleroma.Filter{
- user_id: user.id,
- filter_id: 1,
- phrase: "knights",
- context: ["home"]
- }
+ %{filter_id: id1} = insert(:filter, user: user)
+ %{filter_id: id2} = insert(:filter, user: user)
- query_two = %Pleroma.Filter{
- user_id: user.id,
- filter_id: 2,
- phrase: "who",
- context: ["home"]
- }
+ id1 = to_string(id1)
+ id2 = to_string(id2)
- {:ok, filter_one} = Pleroma.Filter.create(query_one)
- {:ok, filter_two} = Pleroma.Filter.create(query_two)
+ assert [%{"id" => ^id2}, %{"id" => ^id1}] =
+ conn
+ |> get("/api/v1/filters")
+ |> json_response_and_validate_schema(200)
+ end
+
+ test "fetching a list of filters without token", %{conn: conn} do
+ insert(:filter)
response =
conn
|> get("/api/v1/filters")
- |> json_response_and_validate_schema(200)
-
- assert response ==
- render_json(
- FilterView,
- "index.json",
- filters: [filter_two, filter_one]
- )
+ |> json_response(403)
+
+ assert response["error"] == "Invalid credentials."
end
test "get a filter" do
%{user: user, conn: conn} = oauth_access(["read:filters"])
# check whole_word false
- query = %Pleroma.Filter{
- user_id: user.id,
- filter_id: 2,
- phrase: "knight",
- context: ["home"],
- whole_word: false
- }
-
- {:ok, filter} = Pleroma.Filter.create(query)
+ filter = insert(:filter, user: user, whole_word: false)
- conn = get(conn, "/api/v1/filters/#{filter.filter_id}")
+ resp1 =
+ conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(200)
- assert response = json_response_and_validate_schema(conn, 200)
- assert response["whole_word"] == false
+ assert resp1["whole_word"] == false
# check whole_word true
- %{user: user, conn: conn} = oauth_access(["read:filters"])
-
- query = %Pleroma.Filter{
- user_id: user.id,
- filter_id: 3,
- phrase: "knight",
- context: ["home"],
- whole_word: true
- }
+ filter = insert(:filter, user: user, whole_word: true)
- {:ok, filter} = Pleroma.Filter.create(query)
+ resp2 =
+ conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(200)
- conn = get(conn, "/api/v1/filters/#{filter.filter_id}")
-
- assert response = json_response_and_validate_schema(conn, 200)
- assert response["whole_word"] == true
+ assert resp2["whole_word"] == true
end
- test "update a filter" do
- %{user: user, conn: conn} = oauth_access(["write:filters"])
+ test "get a filter not_found error" do
+ filter = insert(:filter)
+ %{conn: conn} = oauth_access(["read:filters"])
- query = %Pleroma.Filter{
- user_id: user.id,
- filter_id: 2,
- phrase: "knight",
- context: ["home"],
- hide: true,
- whole_word: true
- }
+ response =
+ conn |> get("/api/v1/filters/#{filter.filter_id}") |> json_response_and_validate_schema(404)
- {:ok, _filter} = Pleroma.Filter.create(query)
+ assert response["error"] == "Record not found"
+ end
+
+ describe "updating a filter" do
+ setup do: oauth_access(["write:filters"])
+
+ test "common" do
+ %{conn: conn, user: user} = oauth_access(["write:filters"])
+
+ filter =
+ insert(:filter,
+ user: user,
+ hide: true,
+ whole_word: true
+ )
+
+ params = %{
+ phrase: "nii",
+ context: ["public"],
+ irreversible: false
+ }
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/v1/filters/#{filter.filter_id}", params)
+ |> json_response_and_validate_schema(200)
+
+ assert response["phrase"] == params.phrase
+ assert response["context"] == params.context
+ assert response["irreversible"] == false
+ assert response["whole_word"] == true
+ end
+
+ test "with adding expires_at", %{conn: conn, user: user} do
+ filter = insert(:filter, user: user)
+ in_seconds = 600
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/v1/filters/#{filter.filter_id}", %{
+ phrase: "nii",
+ context: ["public"],
+ expires_in: in_seconds,
+ irreversible: true
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert response["irreversible"] == true
+
+ assert response["expires_at"] ==
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(in_seconds)
+ |> Pleroma.Web.CommonAPI.Utils.to_masto_date()
+
+ filter = Filter.get(response["id"], user)
+
+ id = filter.id
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: id}
+ )
+
+ assert {:ok, %{id: ^id}} =
+ perform_job(PurgeExpiredFilter, %{
+ filter_id: id
+ })
+
+ assert Repo.aggregate(Filter, :count, :id) == 0
+ end
+
+ test "with removing expires_at", %{conn: conn, user: user} do
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/filters", %{
+ phrase: "cofe",
+ context: ["home"],
+ expires_in: 600
+ })
+ |> json_response_and_validate_schema(200)
+
+ filter = Filter.get(response["id"], user)
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/v1/filters/#{filter.filter_id}", %{
+ phrase: "nii",
+ context: ["public"],
+ expires_in: nil,
+ whole_word: true
+ })
+ |> json_response_and_validate_schema(200)
+
+ refute_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ assert response["irreversible"] == false
+ assert response["whole_word"] == true
+ assert response["expires_at"] == nil
+ end
+
+ test "expires_at is the same in create and update so job is in db", %{conn: conn, user: user} do
+ resp1 =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/filters", %{
+ phrase: "cofe",
+ context: ["home"],
+ expires_in: 600
+ })
+ |> json_response_and_validate_schema(200)
+
+ filter = Filter.get(resp1["id"], user)
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ job = PurgeExpiredFilter.get_expiration(filter.id)
+
+ resp2 =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/v1/filters/#{filter.filter_id}", %{
+ phrase: "nii",
+ context: ["public"]
+ })
+ |> json_response_and_validate_schema(200)
+
+ updated_filter = Filter.get(resp2["id"], user)
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: updated_filter.id}
+ )
+
+ after_update = PurgeExpiredFilter.get_expiration(updated_filter.id)
+
+ assert resp1["expires_at"] == resp2["expires_at"]
+
+ assert job.scheduled_at == after_update.scheduled_at
+ end
+
+ test "updating expires_at updates oban job too", %{conn: conn, user: user} do
+ resp1 =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/filters", %{
+ phrase: "cofe",
+ context: ["home"],
+ expires_in: 600
+ })
+ |> json_response_and_validate_schema(200)
+
+ filter = Filter.get(resp1["id"], user)
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ job = PurgeExpiredFilter.get_expiration(filter.id)
+
+ resp2 =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/v1/filters/#{filter.filter_id}", %{
+ phrase: "nii",
+ context: ["public"],
+ expires_in: 300
+ })
+ |> json_response_and_validate_schema(200)
+
+ updated_filter = Filter.get(resp2["id"], user)
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: updated_filter.id}
+ )
+
+ after_update = PurgeExpiredFilter.get_expiration(updated_filter.id)
+
+ refute resp1["expires_at"] == resp2["expires_at"]
+
+ refute job.scheduled_at == after_update.scheduled_at
+ end
+ end
- new = %Pleroma.Filter{
- phrase: "nii",
- context: ["home"]
- }
+ test "update filter without token", %{conn: conn} do
+ filter = insert(:filter)
- conn =
+ response =
conn
|> put_req_header("content-type", "application/json")
- |> put("/api/v1/filters/#{query.filter_id}", %{
- phrase: new.phrase,
- context: new.context
+ |> put("/api/v1/filters/#{filter.filter_id}", %{
+ phrase: "nii",
+ context: ["public"]
})
+ |> json_response(403)
- assert response = json_response_and_validate_schema(conn, 200)
- assert response["phrase"] == new.phrase
- assert response["context"] == new.context
- assert response["irreversible"] == true
- assert response["whole_word"] == true
+ assert response["error"] == "Invalid credentials."
end
- test "delete a filter" do
- %{user: user, conn: conn} = oauth_access(["write:filters"])
-
- query = %Pleroma.Filter{
- user_id: user.id,
- filter_id: 2,
- phrase: "knight",
- context: ["home"]
- }
+ describe "delete a filter" do
+ setup do: oauth_access(["write:filters"])
+
+ test "common", %{conn: conn, user: user} do
+ filter = insert(:filter, user: user)
+
+ assert conn
+ |> delete("/api/v1/filters/#{filter.filter_id}")
+ |> json_response_and_validate_schema(200) == %{}
+
+ assert Repo.all(Filter) == []
+ end
+
+ test "with expires_at", %{conn: conn, user: user} do
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/filters", %{
+ phrase: "cofe",
+ context: ["home"],
+ expires_in: 600
+ })
+ |> json_response_and_validate_schema(200)
+
+ filter = Filter.get(response["id"], user)
+
+ assert_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ assert conn
+ |> delete("/api/v1/filters/#{filter.filter_id}")
+ |> json_response_and_validate_schema(200) == %{}
+
+ refute_enqueued(
+ worker: PurgeExpiredFilter,
+ args: %{filter_id: filter.id}
+ )
+
+ assert Repo.all(Filter) == []
+ assert Repo.all(Oban.Job) == []
+ end
+ end
- {:ok, filter} = Pleroma.Filter.create(query)
+ test "delete a filter without token", %{conn: conn} do
+ filter = insert(:filter)
- conn = delete(conn, "/api/v1/filters/#{filter.filter_id}")
+ response =
+ conn
+ |> delete("/api/v1/filters/#{filter.filter_id}")
+ |> json_response(403)
- assert json_response_and_validate_schema(conn, 200) == %{}
+ assert response["error"] == "Invalid credentials."
end
end
end)
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
- refute response["poll"]["expred"]
+ assert response["poll"]["expired"] == false
question = Object.get_by_id(response["poll"]["id"])
%{"error" => error} = json_response_and_validate_schema(conn, 422)
assert error == "Expiration date is too far in the future"
end
+
+ test "scheduled poll", %{conn: conn} do
+ clear_config([ScheduledActivity, :enabled], true)
+
+ scheduled_at =
+ NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
+ |> NaiveDateTime.to_iso8601()
+ |> Kernel.<>("Z")
+
+ %{"id" => scheduled_id} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "very cool poll",
+ "poll" => %{
+ "options" => ~w(a b c),
+ "expires_in" => 420
+ },
+ "scheduled_at" => scheduled_at
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert {:ok, %{id: activity_id}} =
+ perform_job(Pleroma.Workers.ScheduledActivityWorker, %{
+ activity_id: scheduled_id
+ })
+
+ assert Repo.all(Oban.Job) == []
+
+ object =
+ Activity
+ |> Repo.get(activity_id)
+ |> Object.normalize()
+
+ assert object.data["content"] == "very cool poll"
+ assert object.data["type"] == "Question"
+ assert length(object.data["oneOf"]) == 3
+ end
end
test "get a status" do
--- /dev/null
+defmodule Pleroma.Workers.PurgeExpiredFilterTest do
+ use Pleroma.DataCase, async: true
+ use Oban.Testing, repo: Repo
+
+ import Pleroma.Factory
+
+ test "purges expired filter" do
+ %{id: user_id} = insert(:user)
+
+ {:ok, %{id: id}} =
+ Pleroma.Filter.create(%{
+ user_id: user_id,
+ phrase: "cofe",
+ context: ["home"],
+ expires_in: 600
+ })
+
+ assert_enqueued(
+ worker: Pleroma.Workers.PurgeExpiredFilter,
+ args: %{filter_id: id}
+ )
+
+ assert {:ok, %{id: ^id}} =
+ perform_job(Pleroma.Workers.PurgeExpiredFilter, %{
+ filter_id: id
+ })
+
+ assert Repo.aggregate(Pleroma.Filter, :count, :id) == 0
+ end
+end
import Pleroma.Factory
import ExUnit.CaptureLog
- setup do: clear_config([ScheduledActivity, :enabled])
+ setup do: clear_config([ScheduledActivity, :enabled], true)
test "creates a status from the scheduled activity" do
- clear_config([ScheduledActivity, :enabled], true)
user = insert(:user)
naive_datetime =
params: %{status: "hi"}
)
- ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}})
+ {:ok, %{id: activity_id}} =
+ ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}})
refute Repo.get(ScheduledActivity, scheduled_activity.id)
- activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
- assert Pleroma.Object.normalize(activity, fetch: false).data["content"] == "hi"
- end
- test "adds log message if ScheduledActivity isn't find" do
- clear_config([ScheduledActivity, :enabled], true)
+ object =
+ Pleroma.Activity
+ |> Repo.get(activity_id)
+ |> Pleroma.Object.normalize()
+
+ assert object.data["content"] == "hi"
+ end
+ test "error message for non-existent scheduled activity" do
assert capture_log([level: :error], fn ->
ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => 42}})
- end) =~ "Couldn't find scheduled activity"
+ end) =~ "Couldn't find scheduled activity: 42"
end
end
%Pleroma.Filter{
user: build(:user),
filter_id: sequence(:filter_id, & &1),
- phrase: "cofe"
+ phrase: "cofe",
+ context: ["home"]
}
end
end