Merge branch 'develop' into bugfix/web-notification-special-char

This commit is contained in:
Sachin Joshi 2019-05-01 00:21:30 +05:45
commit cd6da3606b
108 changed files with 1517 additions and 501 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@
/db /db
/deps /deps
/*.ez /*.ez
/uploads
/test/uploads /test/uploads
/.elixir_ls /.elixir_ls
/test/fixtures/test_tmp.txt /test/fixtures/test_tmp.txt

View File

@ -16,11 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `link_name` option - Configuration: `link_name` option
- Configuration: `fetch_initial_posts` option - Configuration: `fetch_initial_posts` option
- Configuration: `notify_email` option - Configuration: `notify_email` option
- Pleroma API: User subscribtions - Configuration: Media proxy `whitelist` option
- Pleroma API: User subscriptions
- Pleroma API: Healthcheck endpoint
- Admin API: Endpoints for listing/revoking invite tokens - Admin API: Endpoints for listing/revoking invite tokens
- Admin API: Endpoints for making users follow/unfollow each other - Admin API: Endpoints for making users follow/unfollow each other
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/) - Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension) - Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/) - Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
- ActivityPub C2S: OAuth endpoints - ActivityPub C2S: OAuth endpoints
- Metadata RelMe provider - Metadata RelMe provider
@ -38,11 +41,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: Dedupe enabled by default - Configuration: Dedupe enabled by default
- Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work. - Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
- Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change - Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
- Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
- Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications` - Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
- Mastodon API: Add `languages` and `registrations` to `/api/v1/instance` - Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
- Mastodon API: Provide plaintext versions of cw/content in the Status entity - Mastodon API: Provide plaintext versions of cw/content in the Status entity
- Mastodon API: Add `pleroma.conversation_id` field to the Status entity - Mastodon API: Add `pleroma.conversation_id`, `pleroma.in_reply_to_account_acct` fields to the Status entity
- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending` fields to the User entity - Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending`, `pleroma.hide_followers`, `pleroma.hide_follows`, `pleroma.hide_favorites` fields to the User entity
- Mastodon API: Add `pleroma.show_role`, `pleroma.no_rich_text` fields to the Source subentity
- Mastodon API: Add support for updating `no_rich_text`, `hide_followers`, `hide_follows`, `hide_favorites`, `show_role` in `PATCH /api/v1/update_credentials`
- Mastodon API: Add `pleroma.is_seen` to the Notification entity - Mastodon API: Add `pleroma.is_seen` to the Notification entity
- Mastodon API: Add `pleroma.local` to the Status entity - Mastodon API: Add `pleroma.local` to the Status entity
- Mastodon API: Add `preview` parameter to `POST /api/v1/statuses` - Mastodon API: Add `preview` parameter to `POST /api/v1/statuses`
@ -52,6 +58,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Deps: Updated Cowboy to 2.6 - Deps: Updated Cowboy to 2.6
- Deps: Updated Ecto to 3.0.7 - Deps: Updated Ecto to 3.0.7
- Don't ship finmoji by default, they can be installed as an emoji pack - Don't ship finmoji by default, they can be installed as an emoji pack
- Mastodon API: Added support max_id & since_id for bookmark timeline endpoints.
### Fixed ### Fixed
- Followers counter not being updated when a follower is blocked - Followers counter not being updated when a follower is blocked
@ -66,16 +73,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Federation: Cope with missing or explicitly nulled address lists - Federation: Cope with missing or explicitly nulled address lists
- Federation: Explicitly ensure activities addressed to `as:Public` become addressed to the followers collection - Federation: Explicitly ensure activities addressed to `as:Public` become addressed to the followers collection
- Federation: Better cope with actors which do not declare a followers collection and use `as:Public` with these semantics - Federation: Better cope with actors which do not declare a followers collection and use `as:Public` with these semantics
- Federation: Follow requests from remote users who have been blocked will be automatically rejected if appropriate
- MediaProxy: Parse name from content disposition headers even for non-whitelisted types - MediaProxy: Parse name from content disposition headers even for non-whitelisted types
- MediaProxy: S3 link encoding - MediaProxy: S3 link encoding
- Rich Media: Reject any data which cannot be explicitly encoded into JSON - Rich Media: Reject any data which cannot be explicitly encoded into JSON
- Pleroma API: Importing follows from Mastodon 2.8+ - Pleroma API: Importing follows from Mastodon 2.8+
- Twitter API: Exposing default scope, `no_rich_text` of the user to anyone
- Twitter API: Returning the `role` object in user entity despite `show_role = false`
- Mastodon API: `/api/v1/favourites` serving only public activities - Mastodon API: `/api/v1/favourites` serving only public activities
- Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies - Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies
- Mastodon API: Streaming API broadcasting wrong activity id - Mastodon API: Streaming API broadcasting wrong activity id
- Mastodon API: 500 errors when requesting a card for a private conversation - Mastodon API: 500 errors when requesting a card for a private conversation
- Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow` - Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow`
- Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON - Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
- Mastodon API: Exposing default scope of the user to anyone
## [0.9.9999] - 2019-04-05 ## [0.9.9999] - 2019-04-05
### Security ### Security

View File

@ -221,7 +221,8 @@
allowed_post_formats: [ allowed_post_formats: [
"text/plain", "text/plain",
"text/html", "text/html",
"text/markdown" "text/markdown",
"text/bbcode"
], ],
mrf_transparency: true, mrf_transparency: true,
autofollowed_nicknames: [], autofollowed_nicknames: [],
@ -230,7 +231,8 @@
welcome_user_nickname: nil, welcome_user_nickname: nil,
welcome_message: nil, welcome_message: nil,
max_report_comment_size: 1000, max_report_comment_size: 1000,
safe_dm_mentions: false safe_dm_mentions: false,
healthcheck: false
config :pleroma, :markup, config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because # XXX - unfortunately, inline images must be enabled by default right now, because
@ -325,7 +327,8 @@
follow_redirect: true, follow_redirect: true,
pool: :media pool: :media
] ]
] ],
whitelist: []
config :pleroma, :chat, enabled: true config :pleroma, :chat, enabled: true

View File

@ -20,6 +20,7 @@ Has these additional fields under the `pleroma` object:
- `local`: true if the post was made on the local instance. - `local`: true if the post was made on the local instance.
- `conversation_id`: the ID of the conversation the status is associated with (if any) - `conversation_id`: the ID of the conversation the status is associated with (if any)
- `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any)
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
@ -37,9 +38,18 @@ Has these additional fields under the `pleroma` object:
- `tags`: Lists an array of tags for the user - `tags`: Lists an array of tags for the user
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship - `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship
- `is_moderator`: boolean, true if user is a moderator - `is_moderator`: boolean, nullable, true if user is a moderator
- `is_admin`: boolean, true if user is an admin - `is_admin`: boolean, nullable, true if user is an admin
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated - `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
- `hide_followers`: boolean, true when the user has follower hiding enabled
- `hide_follows`: boolean, true when the user has follow hiding enabled
### Source
Has these additional fields under the `pleroma` object:
- `show_role`: boolean, nullable, true when the user wants his role (e.g admin, moderator) to be shown
- `no_rich_text` - boolean, nullable, true when html tags are stripped from all statuses requested from the API
## Account Search ## Account Search
@ -59,3 +69,14 @@ Additional parameters can be added to the JSON body/Form data:
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. - `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
- `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
## PATCH `/api/v1/update_credentials`
Additional parameters can be added to the JSON body/Form data:
- `no_rich_text` - if true, html tags are stripped from all statuses requested from the API
- `hide_followers` - if true, user's followers will be hidden
- `hide_follows` - if true, user's follows will be hidden
- `hide_favorites` - if true, user's favorites timeline will be hidden
- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
- `default_scope` - the scope returned under `privacy` key in Source subentity

View File

@ -77,7 +77,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
* `token`: invite token required when the registrations aren't public. * `token`: invite token required when the registrations aren't public.
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}` * Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
* Example response: * Example response:
``` ```json
{ {
"background_image": null, "background_image": null,
"cover_photo": "https://pleroma.soykaf.com/images/banner.png", "cover_photo": "https://pleroma.soykaf.com/images/banner.png",
@ -187,6 +187,62 @@ See [Admin-API](Admin-API.md)
} }
``` ```
## `/api/v1/pleroma/accounts/:id/favourites`
### Returns favorites timeline of any user
* Method `GET`
* Authentication: not required
* Params:
* `id`: the id of the account for whom to return results
* `limit`: optional, the number of records to retrieve
* `since_id`: optional, returns results that are more recent than the specified id
* `max_id`: optional, returns results that are older than the specified id
* Response: JSON, returns a list of Mastodon Status entities on success, otherwise returns `{"error": "error_msg"}`
* Example response:
```json
[
{
"account": {
"id": "9hptFmUF3ztxYh3Svg",
"url": "https://pleroma.example.org/users/nick2",
"username": "nick2",
...
},
"application": {"name": "Web", "website": null},
"bookmarked": false,
"card": null,
"content": "This is :moominmamma: note 0",
"created_at": "2019-04-15T15:42:15.000Z",
"emojis": [],
"favourited": false,
"favourites_count": 1,
"id": "9hptFmVJ02khbzYJaS",
"in_reply_to_account_id": null,
"in_reply_to_id": null,
"language": null,
"media_attachments": [],
"mentions": [],
"muted": false,
"pinned": false,
"pleroma": {
"content": {"text/plain": "This is :moominmamma: note 0"},
"conversation_id": 13679,
"local": true,
"spoiler_text": {"text/plain": "2hu"}
},
"reblog": null,
"reblogged": false,
"reblogs_count": 0,
"replies_count": 0,
"sensitive": false,
"spoiler_text": "2hu",
"tags": [{"name": "2hu", "url": "/tag/2hu"}],
"uri": "https://pleroma.example.org/objects/198ed2a1-7912-4482-b559-244a0369e984",
"url": "https://pleroma.example.org/notice/9hptFmVJ02khbzYJaS",
"visibility": "public"
}
]
```
## `/api/pleroma/notification_settings` ## `/api/pleroma/notification_settings`
### Updates user notification settings ### Updates user notification settings
* Method `PUT` * Method `PUT`
@ -197,3 +253,20 @@ See [Admin-API](Admin-API.md)
* `remote`: BOOLEAN field, receives notifications from people on remote instances * `remote`: BOOLEAN field, receives notifications from people on remote instances
* `local`: BOOLEAN field, receives notifications from people on the local instance * `local`: BOOLEAN field, receives notifications from people on the local instance
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}` * Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
## `/api/pleroma/healthcheck`
### Healthcheck endpoint with additional system data.
* Method `GET`
* Authentication: not required
* Params: none
* Response: JSON, statuses (200 - healthy, 503 unhealthy).
* Example response:
```json
{
"pool_size": 0, # database connection pool
"active": 0, # active processes
"idle": 0, # idle processes
"memory_used": 0.00, # Memory used
"healthy": true # Instance state
}
```

View File

@ -103,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message. * `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`) * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`) * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
## :logger ## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack * `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
@ -204,6 +205,7 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
* `enabled`: Enables proxying of remote media to the instances proxy * `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
* `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`. * `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`.
* `whitelist`: List of domains to bypass the mediaproxy
## :gopher ## :gopher
* `enabled`: Enables the gopher interface * `enabled`: Enables the gopher interface
@ -486,3 +488,8 @@ config :ueberauth, Ueberauth,
microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]} microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
] ]
``` ```
## :emoji
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`
* `groups`: Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname and the value the location or array of locations. `*` can be used as a wildcard. Example `[Custom: ["/emoji/*.png", "/emoji/custom/*.png"]]`
* `default_manifest`: Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays).

View File

@ -28,6 +28,11 @@ foo, /emoji/custom/foo.png
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon. The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
Default file extentions and locations for emojis are set in `config.exs`. To use different locations or file-extentions, add the `shortcode_globs` to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. Note that not all fediverse-software will show emojis with other file extentions:
```elixir
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png", "/emoji/custom/**/*.gif"]
```
## Emoji tags (groups) ## Emoji tags (groups)
Default tags are set in `config.exs`. To set your own tags, copy the structure to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it. Default tags are set in `config.exs`. To set your own tags, copy the structure to your secrets file (`prod.secret.exs` or `dev.secret.exs`) and edit it.

60
lib/healthcheck.ex Normal file
View File

@ -0,0 +1,60 @@
defmodule Pleroma.Healthcheck do
@moduledoc """
Module collects metrics about app and assign healthy status.
"""
alias Pleroma.Healthcheck
alias Pleroma.Repo
defstruct pool_size: 0,
active: 0,
idle: 0,
memory_used: 0,
healthy: true
@type t :: %__MODULE__{
pool_size: non_neg_integer(),
active: non_neg_integer(),
idle: non_neg_integer(),
memory_used: number(),
healthy: boolean()
}
@spec system_info() :: t()
def system_info do
%Healthcheck{
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2)
}
|> assign_db_info()
|> check_health()
end
defp assign_db_info(healthcheck) do
database = Application.get_env(:pleroma, Repo)[:database]
query =
"select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;"
result = Repo.query!(query)
pool_size = Application.get_env(:pleroma, Repo)[:pool_size]
db_info =
Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states ->
if state == "active" do
Map.put(states, :active, states.active + cnt)
else
Map.put(states, :idle, states.idle + cnt)
end
end)
|> Map.put(:pool_size, pool_size)
Map.merge(healthcheck, db_info)
end
@spec check_health(Healthcheck.t()) :: Healthcheck.t()
def check_health(%{pool_size: pool_size, active: active} = check)
when active >= pool_size do
%{check | healthy: false}
end
def check_health(check), do: check
end

View File

@ -11,7 +11,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
## ls-packs ## ls-packs
mix pleroma.emoji ls-packs [OPTION...] mix pleroma.emoji ls-packs [OPTION...]
Lists the emoji packs and metadata specified in the manifest. Lists the emoji packs and metadata specified in the manifest.
@ -23,10 +23,10 @@ defmodule Mix.Tasks.Pleroma.Emoji do
## get-packs ## get-packs
mix pleroma.emoji get-packs [OPTION...] PACKS mix pleroma.emoji get-packs [OPTION...] PACKS
Fetches, verifies and installs the specified PACKS from the Fetches, verifies and installs the specified PACKS from the
manifest into the `STATIC-DIR/emoji/PACK-NAME manifest into the `STATIC-DIR/emoji/PACK-NAME`
### Options ### Options
@ -34,7 +34,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
## gen-pack ## gen-pack
mix pleroma.emoji gen-pack PACK-URL mix pleroma.emoji gen-pack PACK-URL
Creates a new manifest entry and a file list from the specified Creates a new manifest entry and a file list from the specified
remote pack file. Currently, only .zip archives are recognized remote pack file. Currently, only .zip archives are recognized

View File

@ -162,7 +162,7 @@ def run(["new", nickname, email | rest]) do
def run(["rm", nickname]) do def run(["rm", nickname]) do
Common.start_pleroma() Common.start_pleroma()
with %User{local: true} = user <- User.get_by_nickname(nickname) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
User.delete(user) User.delete(user)
Mix.shell().info("User #{nickname} deleted.") Mix.shell().info("User #{nickname} deleted.")
else else
@ -174,7 +174,7 @@ def run(["rm", nickname]) do
def run(["toggle_activated", nickname]) do def run(["toggle_activated", nickname]) do
Common.start_pleroma() Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.deactivate(user, !user.info.deactivated) {:ok, user} = User.deactivate(user, !user.info.deactivated)
Mix.shell().info( Mix.shell().info(
@ -189,7 +189,7 @@ def run(["toggle_activated", nickname]) do
def run(["reset_password", nickname]) do def run(["reset_password", nickname]) do
Common.start_pleroma() Common.start_pleroma()
with %User{local: true} = user <- User.get_by_nickname(nickname), with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
Mix.shell().info("Generated password reset token for #{user.nickname}") Mix.shell().info("Generated password reset token for #{user.nickname}")
@ -211,14 +211,14 @@ def run(["reset_password", nickname]) do
def run(["unsubscribe", nickname]) do def run(["unsubscribe", nickname]) do
Common.start_pleroma() Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do
Mix.shell().info("Deactivating #{user.nickname}") Mix.shell().info("Deactivating #{user.nickname}")
User.deactivate(user) User.deactivate(user)
{:ok, friends} = User.get_friends(user) {:ok, friends} = User.get_friends(user)
Enum.each(friends, fn friend -> Enum.each(friends, fn friend ->
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}") Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
User.unfollow(user, friend) User.unfollow(user, friend)
@ -226,7 +226,7 @@ def run(["unsubscribe", nickname]) do
:timer.sleep(500) :timer.sleep(500)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
if Enum.empty?(user.following) do if Enum.empty?(user.following) do
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}") Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
@ -250,7 +250,7 @@ def run(["set", nickname | rest]) do
] ]
) )
with %User{local: true} = user <- User.get_by_nickname(nickname) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
user = user =
case Keyword.get(options, :moderator) do case Keyword.get(options, :moderator) do
nil -> user nil -> user
@ -277,7 +277,7 @@ def run(["set", nickname | rest]) do
def run(["tag", nickname | tags]) do def run(["tag", nickname | tags]) do
Common.start_pleroma() Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.tag(tags) user = user |> User.tag(tags)
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
@ -290,7 +290,7 @@ def run(["tag", nickname | tags]) do
def run(["untag", nickname | tags]) do def run(["untag", nickname | tags]) do
Common.start_pleroma() Common.start_pleroma()
with %User{} = user <- User.get_by_nickname(nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.untag(tags) user = user |> User.untag(tags)
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
@ -379,7 +379,7 @@ def run(["revoke_invite", token]) do
def run(["delete_activities", nickname]) do def run(["delete_activities", nickname]) do
Common.start_pleroma() Common.start_pleroma()
with %User{local: true} = user <- User.get_by_nickname(nickname) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
User.delete_user_activities(user) User.delete_user_activities(user)
Mix.shell().info("User #{nickname} statuses deleted.") Mix.shell().info("User #{nickname} statuses deleted.")
else else

View File

@ -39,7 +39,7 @@ def used_changeset(struct) do
def reset_password(token, data) do def reset_password(token, data) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
%User{} = user <- User.get_by_id(token.user_id), %User{} = user <- User.get_cached_by_id(token.user_id),
{:ok, _user} <- User.reset_password(user, data), {:ok, _user} <- User.reset_password(user, data),
{:ok, token} <- Repo.update(used_changeset(token)) do {:ok, token} <- Repo.update(used_changeset(token)) do
{:ok, token} {:ok, token}

60
lib/pleroma/bookmark.ex Normal file
View File

@ -0,0 +1,60 @@
defmodule Pleroma.Bookmark do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.FlakeId
alias Pleroma.Repo
alias Pleroma.User
@type t :: %__MODULE__{}
schema "bookmarks" do
belongs_to(:user, User, type: FlakeId)
belongs_to(:activity, Activity, type: FlakeId)
timestamps()
end
@spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
def create(user_id, activity_id) do
attrs = %{
user_id: user_id,
activity_id: activity_id
}
%Bookmark{}
|> cast(attrs, [:user_id, :activity_id])
|> validate_required([:user_id, :activity_id])
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|> Repo.insert()
end
@spec for_user_query(FlakeId.t()) :: Ecto.Query.t()
def for_user_query(user_id) do
Bookmark
|> where(user_id: ^user_id)
|> join(:inner, [b], activity in assoc(b, :activity))
|> preload([b, a], activity: a)
end
def get(user_id, activity_id) do
Bookmark
|> where(user_id: ^user_id)
|> where(activity_id: ^activity_id)
|> Repo.one()
end
@spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
def destroy(user_id, activity_id) do
from(b in Bookmark,
where: b.user_id == ^user_id,
where: b.activity_id == ^activity_id
)
|> Repo.one()
|> Repo.delete()
end
end

View File

@ -76,7 +76,7 @@ def render_activities(activities) do
|> Enum.map(fn activity -> |> Enum.map(fn activity ->
user = User.get_cached_by_ap_id(activity.data["actor"]) user = User.get_cached_by_ap_id(activity.data["actor"])
object = Object.normalize(activity.data["object"]) object = Object.normalize(activity)
like_count = object["like_count"] || 0 like_count = object["like_count"] || 0
announcement_count = object["announcement_count"] || 0 announcement_count = object["announcement_count"] || 0

View File

@ -106,7 +106,14 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
# links # links
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
Meta.allow_tag_with_this_attribute_values("a", "class", [
"hashtag",
"u-url",
"mention",
"u-url mention",
"mention u-url"
])
Meta.allow_tag_with_this_attribute_values("a", "rel", [ Meta.allow_tag_with_this_attribute_values("a", "rel", [
"tag", "tag",
@ -115,12 +122,15 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
"noreferrer" "noreferrer"
]) ])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
# paragraphs and linebreaks # paragraphs and linebreaks
Meta.allow_tag_with_these_attributes("br", []) Meta.allow_tag_with_these_attributes("br", [])
Meta.allow_tag_with_these_attributes("p", []) Meta.allow_tag_with_these_attributes("p", [])
# microformats # microformats
Meta.allow_tag_with_these_attributes("span", ["class"]) Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
Meta.allow_tag_with_these_attributes("span", [])
# allow inline images for custom emoji # allow inline images for custom emoji
@allow_inline_images Keyword.get(@markup, :allow_inline_images) @allow_inline_images Keyword.get(@markup, :allow_inline_images)
@ -155,7 +165,14 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.strip_comments() Meta.strip_comments()
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
Meta.allow_tag_with_this_attribute_values("a", "class", [
"hashtag",
"u-url",
"mention",
"u-url mention",
"mention u-url"
])
Meta.allow_tag_with_this_attribute_values("a", "rel", [ Meta.allow_tag_with_this_attribute_values("a", "rel", [
"tag", "tag",
@ -164,6 +181,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
"noreferrer" "noreferrer"
]) ])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
Meta.allow_tag_with_these_attributes("abbr", ["title"]) Meta.allow_tag_with_these_attributes("abbr", ["title"])
Meta.allow_tag_with_these_attributes("b", []) Meta.allow_tag_with_these_attributes("b", [])
@ -177,11 +196,13 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("ol", []) Meta.allow_tag_with_these_attributes("ol", [])
Meta.allow_tag_with_these_attributes("p", []) Meta.allow_tag_with_these_attributes("p", [])
Meta.allow_tag_with_these_attributes("pre", []) Meta.allow_tag_with_these_attributes("pre", [])
Meta.allow_tag_with_these_attributes("span", ["class"])
Meta.allow_tag_with_these_attributes("strong", []) Meta.allow_tag_with_these_attributes("strong", [])
Meta.allow_tag_with_these_attributes("u", []) Meta.allow_tag_with_these_attributes("u", [])
Meta.allow_tag_with_these_attributes("ul", []) Meta.allow_tag_with_these_attributes("ul", [])
Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
Meta.allow_tag_with_these_attributes("span", [])
@allow_inline_images Keyword.get(@markup, :allow_inline_images) @allow_inline_images Keyword.get(@markup, :allow_inline_images)
if @allow_inline_images do if @allow_inline_images do

View File

@ -80,7 +80,7 @@ def get_lists_from_activity(%Activity{actor: ap_id}) do
# Get lists to which the account belongs. # Get lists to which the account belongs.
def get_lists_account_belongs(%User{} = owner, account_id) do def get_lists_account_belongs(%User{} = owner, account_id) do
user = User.get_by_id(account_id) user = User.get_cached_by_id(account_id)
query = query =
from( from(

View File

@ -196,7 +196,7 @@ def skip?(
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
actor = activity.data["actor"] actor = activity.data["actor"]
followed = User.get_by_ap_id(actor) followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed) User.following?(user, followed)
end end

View File

@ -39,7 +39,7 @@ def fetch_object_from_id(id) do
Logger.info("Couldn't get object via AP, trying out OStatus fetching...") Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
case OStatus.fetch_activity_from_url(id) do case OStatus.fetch_activity_from_url(id) do
{:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"], false)} {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
e -> e e -> e
end end
end end

View File

@ -10,6 +10,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
@ -53,8 +54,8 @@ defmodule Pleroma.User do
field(:search_rank, :float, virtual: true) field(:search_rank, :float, virtual: true)
field(:search_type, :integer, virtual: true) field(:search_type, :integer, virtual: true)
field(:tags, {:array, :string}, default: []) field(:tags, {:array, :string}, default: [])
field(:bookmarks, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec) field(:last_refreshed_at, :naive_datetime_usec)
has_many(:bookmarks, Bookmark)
has_many(:notifications, Notification) has_many(:notifications, Notification)
has_many(:registrations, Registration) has_many(:registrations, Registration)
embeds_one(:info, Pleroma.User.Info) embeds_one(:info, Pleroma.User.Info)
@ -269,6 +270,7 @@ defp autofollow_users(user) do
def register(%Ecto.Changeset{} = changeset) do def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset), with {:ok, user} <- Repo.insert(changeset),
{:ok, user} <- autofollow_users(user), {:ok, user} <- autofollow_users(user),
{:ok, user} <- set_cache(user),
{:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user), {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
{:ok, _} <- try_send_confirmation_email(user) do {:ok, _} <- try_send_confirmation_email(user) do
{:ok, user} {:ok, user}
@ -453,10 +455,13 @@ def get_by_guessed_nickname(ap_id) do
name = List.last(String.split(ap_id, "/")) name = List.last(String.split(ap_id, "/"))
nickname = "#{name}@#{domain}" nickname = "#{name}@#{domain}"
get_by_nickname(nickname) get_cached_by_nickname(nickname)
end end
def set_cache(user) do def set_cache({:ok, user}), do: set_cache(user)
def set_cache({:error, err}), do: {:error, err}
def set_cache(%User{} = user) do
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
Cachex.put(:user_cache, "nickname:#{user.nickname}", user) Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
@ -544,6 +549,7 @@ def get_or_fetch_by_nickname(nickname) do
with [_nick, _domain] <- String.split(nickname, "@"), with [_nick, _domain] <- String.split(nickname, "@"),
{:ok, user} <- fetch_by_nickname(nickname) do {:ok, user} <- fetch_by_nickname(nickname) do
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
# TODO turn into job
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user]) {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
end end
@ -1002,7 +1008,7 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
# helper to handle the block given only an actor's AP id # helper to handle the block given only an actor's AP id
def block(blocker, %{ap_id: ap_id}) do def block(blocker, %{ap_id: ap_id}) do
block(blocker, User.get_by_ap_id(ap_id)) block(blocker, get_cached_by_ap_id(ap_id))
end end
def unblock(blocker, %{ap_id: ap_id}) do def unblock(blocker, %{ap_id: ap_id}) do
@ -1032,7 +1038,7 @@ def blocks?(user, %{ap_id: ap_id}) do
end end
def subscribed_to?(user, %{ap_id: ap_id}) do def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- User.get_by_ap_id(ap_id) do with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.info.subscribers, user.ap_id) Enum.member?(target.info.subscribers, user.ap_id)
end end
end end
@ -1207,7 +1213,7 @@ def fetch_by_ap_id(ap_id) do
end end
def get_or_fetch_by_ap_id(ap_id) do def get_or_fetch_by_ap_id(ap_id) do
user = get_by_ap_id(ap_id) user = get_cached_by_ap_id(ap_id)
if !is_nil(user) and !User.needs_update?(user) do if !is_nil(user) and !User.needs_update?(user) do
user user
@ -1230,7 +1236,7 @@ def get_or_fetch_by_ap_id(ap_id) do
def get_or_create_instance_user do def get_or_create_instance_user do
relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay" relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
if user = get_by_ap_id(relay_uri) do if user = get_cached_by_ap_id(relay_uri) do
user user
else else
changes = changes =
@ -1277,13 +1283,11 @@ defp blank?(""), do: nil
defp blank?(n), do: n defp blank?(n), do: n
def insert_or_update_user(data) do def insert_or_update_user(data) do
data = data
data |> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> Map.put(:name, blank?(data[:name]) || data[:nickname]) |> remote_user_creation()
|> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname)
cs = User.remote_user_creation(data) |> set_cache()
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
end end
def ap_enabled?(%User{local: true}), do: true def ap_enabled?(%User{local: true}), do: true
@ -1299,8 +1303,8 @@ def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
# this is because we have synchronous follow APIs and need to simulate them # this is because we have synchronous follow APIs and need to simulate them
# with an async handshake # with an async handshake
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
with %User{} = a <- User.get_by_id(a.id), with %User{} = a <- User.get_cached_by_id(a.id),
%User{} = b <- User.get_by_id(b.id) do %User{} = b <- User.get_cached_by_id(b.id) do
{:ok, a, b} {:ok, a, b}
else else
_e -> _e ->
@ -1310,8 +1314,8 @@ def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
with :ok <- :timer.sleep(timeout), with :ok <- :timer.sleep(timeout),
%User{} = a <- User.get_by_id(a.id), %User{} = a <- User.get_cached_by_id(a.id),
%User{} = b <- User.get_by_id(b.id) do %User{} = b <- User.get_cached_by_id(b.id) do
{:ok, a, b} {:ok, a, b}
else else
_e -> _e ->
@ -1350,7 +1354,7 @@ def tag(user_identifiers, tags) when is_list(user_identifiers) do
end end
def tag(nickname, tags) when is_binary(nickname), def tag(nickname, tags) when is_binary(nickname),
do: tag(User.get_by_nickname(nickname), tags) do: tag(get_by_nickname(nickname), tags)
def tag(%User{} = user, tags), def tag(%User{} = user, tags),
do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags))) do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags)))
@ -1362,7 +1366,7 @@ def untag(user_identifiers, tags) when is_list(user_identifiers) do
end end
def untag(nickname, tags) when is_binary(nickname), def untag(nickname, tags) when is_binary(nickname),
do: untag(User.get_by_nickname(nickname), tags) do: untag(get_by_nickname(nickname), tags)
def untag(%User{} = user, tags), def untag(%User{} = user, tags),
do: update_tags(user, (user.tags || []) -- normalize_tags(tags)) do: update_tags(user, (user.tags || []) -- normalize_tags(tags))
@ -1376,22 +1380,6 @@ defp update_tags(%User{} = user, new_tags) do
updated_user updated_user
end end
def bookmark(%User{} = user, status_id) do
bookmarks = Enum.uniq(user.bookmarks ++ [status_id])
update_bookmarks(user, bookmarks)
end
def unbookmark(%User{} = user, status_id) do
bookmarks = Enum.uniq(user.bookmarks -- [status_id])
update_bookmarks(user, bookmarks)
end
def update_bookmarks(%User{} = user, bookmarks) do
user
|> change(%{bookmarks: bookmarks})
|> update_and_set_cache
end
defp normalize_tags(tags) do defp normalize_tags(tags) do
[tags] [tags]
|> List.flatten() |> List.flatten()

View File

@ -38,6 +38,7 @@ defmodule Pleroma.User.Info do
field(:salmon, :string, default: nil) field(:salmon, :string, default: nil)
field(:hide_followers, :boolean, default: false) field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false) field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
field(:pinned_activities, {:array, :string}, default: []) field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil) field(:flavour, :string, default: nil)
@ -202,6 +203,7 @@ def profile_update(info, params) do
:banner, :banner,
:hide_follows, :hide_follows,
:hide_followers, :hide_followers,
:hide_favorites,
:background, :background,
:show_role :show_role
]) ])
@ -225,14 +227,6 @@ def confirmation_changeset(info, params) do
cast(info, params, [:confirmation_pending, :confirmation_token]) cast(info, params, [:confirmation_pending, :confirmation_token])
end end
def mastodon_profile_update(info, params) do
info
|> cast(params, [
:locked,
:banner
])
end
def mastodon_settings_update(info, settings) do def mastodon_settings_update(info, settings) do
params = %{settings: settings} params = %{settings: settings}

View File

@ -168,7 +168,6 @@ def stream_out(activity) do
public = "https://www.w3.org/ns/activitystreams#Public" public = "https://www.w3.org/ns/activitystreams#Public"
if activity.data["type"] in ["Create", "Announce", "Delete"] do if activity.data["type"] in ["Create", "Announce", "Delete"] do
object = Object.normalize(activity.data["object"])
Pleroma.Web.Streamer.stream("user", activity) Pleroma.Web.Streamer.stream("user", activity)
Pleroma.Web.Streamer.stream("list", activity) Pleroma.Web.Streamer.stream("list", activity)
@ -180,6 +179,8 @@ def stream_out(activity) do
end end
if activity.data["type"] in ["Create"] do if activity.data["type"] in ["Create"] do
object = Object.normalize(activity)
object.data object.data
|> Map.get("tag", []) |> Map.get("tag", [])
|> Enum.filter(fn tag -> is_bitstring(tag) end) |> Enum.filter(fn tag -> is_bitstring(tag) end)
@ -197,7 +198,7 @@ def stream_out(activity) do
if !Enum.member?(activity.data["cc"] || [], public) && if !Enum.member?(activity.data["cc"] || [], public) &&
!Enum.member?( !Enum.member?(
activity.data["to"], activity.data["to"],
User.get_by_ap_id(activity.data["actor"]).follower_address User.get_cached_by_ap_id(activity.data["actor"]).follower_address
), ),
do: Pleroma.Web.Streamer.stream("direct", activity) do: Pleroma.Web.Streamer.stream("direct", activity)
end end
@ -889,7 +890,7 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
end end
def make_user_from_ap_id(ap_id) do def make_user_from_ap_id(ap_id) do
if _user = User.get_by_ap_id(ap_id) do if _user = User.get_cached_by_ap_id(ap_id) do
Transmogrifier.upgrade_user_from_ap_id(ap_id) Transmogrifier.upgrade_user_from_ap_id(ap_id)
else else
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do

View File

@ -438,20 +438,46 @@ def handle_incoming(
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
%User{} = follower <- User.get_or_fetch_by_ap_id(follower), %User{} = follower <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
if not User.locked?(followed) do with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
{:user_blocked, false} <-
{:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{:user_locked, false} <- {:user_locked, User.locked?(followed)},
{:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
actor: followed, actor: followed,
object: data, object: data,
local: true local: true
}) })
else
{:user_blocked, true} ->
{:ok, _} = Utils.update_follow_state(activity, "reject")
User.follow(follower, followed) ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: data,
local: true
})
{:follow, {:error, _}} ->
{:ok, _} = Utils.update_follow_state(activity, "reject")
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: data,
local: true
})
{:user_locked, true} ->
:noop
end end
{:ok, activity} {:ok, activity}
else else
_e -> :error _e ->
:error
end end
end end
@ -537,7 +563,7 @@ def handle_incoming(
data data
) )
when object_type in ["Person", "Application", "Service", "Organization"] do when object_type in ["Person", "Application", "Service", "Organization"] do
with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
banner = new_user_data[:info]["banner"] banner = new_user_data[:info]["banner"]
@ -964,7 +990,7 @@ def perform(:user_upgrade, user) do
end end
def upgrade_user_from_ap_id(ap_id) do def upgrade_user_from_ap_id(ap_id) do
with %User{local: false} = user <- User.get_by_ap_id(ap_id), with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
already_ap <- User.ap_enabled?(user), already_ap <- User.ap_enabled?(user),
{:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do

View File

@ -19,7 +19,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action_fallback(:errors) action_fallback(:errors)
def user_delete(conn, %{"nickname" => nickname}) do def user_delete(conn, %{"nickname" => nickname}) do
User.get_by_nickname(nickname) User.get_cached_by_nickname(nickname)
|> User.delete() |> User.delete()
conn conn
@ -27,8 +27,8 @@ def user_delete(conn, %{"nickname" => nickname}) do
end end
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
with %User{} = follower <- User.get_by_nickname(follower_nick), with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_by_nickname(followed_nick) do %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.follow(follower, followed) User.follow(follower, followed)
end end
@ -37,8 +37,8 @@ def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick
end end
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
with %User{} = follower <- User.get_by_nickname(follower_nick), with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
%User{} = followed <- User.get_by_nickname(followed_nick) do %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
User.unfollow(follower, followed) User.unfollow(follower, followed)
end end
@ -67,7 +67,7 @@ def user_create(
end end
def user_show(conn, %{"nickname" => nickname}) do def user_show(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_by_nickname(nickname) do with %User{} = user <- User.get_cached_by_nickname(nickname) do
conn conn
|> json(AccountView.render("show.json", %{user: user})) |> json(AccountView.render("show.json", %{user: user}))
else else
@ -76,7 +76,7 @@ def user_show(conn, %{"nickname" => nickname}) do
end end
def user_toggle_activation(conn, %{"nickname" => nickname}) do def user_toggle_activation(conn, %{"nickname" => nickname}) do
user = User.get_by_nickname(nickname) user = User.get_cached_by_nickname(nickname)
{:ok, updated_user} = User.deactivate(user, !user.info.deactivated) {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
@ -131,7 +131,7 @@ defp maybe_parse_filters(filters) do
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
when permission_group in ["moderator", "admin"] do when permission_group in ["moderator", "admin"] do
user = User.get_by_nickname(nickname) user = User.get_cached_by_nickname(nickname)
info = info =
%{} %{}
@ -156,7 +156,7 @@ def right_add(conn, _) do
end end
def right_get(conn, %{"nickname" => nickname}) do def right_get(conn, %{"nickname" => nickname}) do
user = User.get_by_nickname(nickname) user = User.get_cached_by_nickname(nickname)
conn conn
|> json(%{ |> json(%{
@ -178,7 +178,7 @@ def right_delete(
|> put_status(403) |> put_status(403)
|> json(%{error: "You can't revoke your own admin status."}) |> json(%{error: "You can't revoke your own admin status."})
else else
user = User.get_by_nickname(nickname) user = User.get_cached_by_nickname(nickname)
info = info =
%{} %{}
@ -204,7 +204,7 @@ def right_delete(conn, _) do
def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
with {:ok, status} <- Ecto.Type.cast(:boolean, status), with {:ok, status} <- Ecto.Type.cast(:boolean, status),
%User{} = user <- User.get_by_nickname(nickname), %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, _} <- User.deactivate(user, !status), {:ok, _} <- User.deactivate(user, !status),
do: json_response(conn, :no_content, "") do: json_response(conn, :no_content, "")
end end
@ -277,7 +277,7 @@ def revoke_invite(conn, %{"token" => token}) do
@doc "Get a password reset token (base64 string) for given nickname" @doc "Get a password reset token (base64 string) for given nickname"
def get_password_reset(conn, %{"nickname" => nickname}) do def get_password_reset(conn, %{"nickname" => nickname}) do
(%User{local: true} = user) = User.get_by_nickname(nickname) (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
{:ok, token} = Pleroma.PasswordResetToken.create_token(user) {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
conn conn

View File

@ -24,7 +24,7 @@ defmodule Pleroma.Web.UserSocket do
def connect(%{"token" => token}, socket) do def connect(%{"token" => token}, socket) do
with true <- Pleroma.Config.get([:chat, :enabled]), with true <- Pleroma.Config.get([:chat, :enabled]),
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600), {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
%User{} = user <- Pleroma.User.get_by_id(user_id) do %User{} = user <- Pleroma.User.get_cached_by_id(user_id) do
{:ok, assign(socket, :user_name, user.nickname)} {:ok, assign(socket, :user_name, user.nickname)}
else else
_e -> :error _e -> :error

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Web.CommonAPI do defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.ThreadMute alias Pleroma.ThreadMute
@ -282,9 +283,18 @@ def thread_muted?(user, activity) do
end end
end end
def bookmarked?(user, activity) do
with %Bookmark{} <- Bookmark.get(user.id, activity.id) do
true
else
_ ->
false
end
end
def report(user, data) do def report(user, data) do
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data}, with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
{:account, %User{} = account} <- {:account, User.get_by_id(account_id)}, {:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]), {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
{:ok, statuses} <- get_report_statuses(account, data), {:ok, statuses} <- get_report_statuses(account, data),
{:ok, activity} <- {:ok, activity} <-

View File

@ -182,6 +182,18 @@ def format_input(text, "text/plain", options) do
end).() end).()
end end
@doc """
Formatting text as BBCode.
"""
def format_input(text, "text/bbcode", options) do
text
|> String.replace(~r/\r/, "")
|> Formatter.html_escape("text/plain")
|> BBCode.to_html()
|> (fn {:ok, html} -> html end).()
|> Formatter.linkify(options)
end
@doc """ @doc """
Formatting text to html. Formatting text to html.
""" """
@ -226,7 +238,7 @@ def make_note_data(
} }
if in_reply_to do if in_reply_to do
in_reply_to_object = Object.normalize(in_reply_to.data["object"]) in_reply_to_object = Object.normalize(in_reply_to)
object object
|> Map.put("inReplyTo", in_reply_to_object.data["id"]) |> Map.put("inReplyTo", in_reply_to_object.data["id"])
@ -284,7 +296,7 @@ defp shortname(name) do
end end
def confirm_current_password(user, password) do def confirm_current_password(user, password) do
with %User{local: true} = db_user <- User.get_by_id(user.id), with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
true <- Pbkdf2.checkpw(password, db_user.password_hash) do true <- Pbkdf2.checkpw(password, db_user.password_hash) do
{:ok, db_user} {:ok, db_user}
else else

View File

@ -186,7 +186,7 @@ def perform(type, _) do
end end
def ap_enabled_actor(id) do def ap_enabled_actor(id) do
user = User.get_by_ap_id(id) user = User.get_cached_by_ap_id(id)
if User.ap_enabled?(user) do if User.ap_enabled?(user) do
{:ok, user} {:ok, user}

View File

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Filter alias Pleroma.Filter
alias Pleroma.Notification alias Pleroma.Notification
@ -35,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] alias Pleroma.Web.ControllerHelper
import Ecto.Query import Ecto.Query
require Logger require Logger
@ -46,7 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
action_fallback(:errors) action_fallback(:errors)
def create_app(conn, params) do def create_app(conn, params) do
scopes = oauth_scopes(params, ["read"]) scopes = ControllerHelper.oauth_scopes(params, ["read"])
app_attrs = app_attrs =
params params
@ -96,8 +97,13 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
end) end)
info_params = info_params =
%{} [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
|> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end) |> Enum.reduce(%{}, fn key, acc ->
add_if_present(acc, params, to_string(key), key, fn value ->
{:ok, ControllerHelper.truthy_param?(value)}
end)
end)
|> add_if_present(params, "default_scope", :default_scope)
|> add_if_present(params, "header", :banner, fn value -> |> add_if_present(params, "header", :banner, fn value ->
with %Plug.Upload{} <- value, with %Plug.Upload{} <- value,
{:ok, object} <- ActivityPub.upload(value, type: :banner) do {:ok, object} <- ActivityPub.upload(value, type: :banner) do
@ -107,7 +113,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
end end
end) end)
info_cng = User.Info.mastodon_profile_update(user.info, info_params) info_cng = User.Info.profile_update(user.info, info_params)
with changeset <- User.update_changeset(user, user_params), with changeset <- User.update_changeset(user, user_params),
changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
@ -279,6 +285,8 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|> ActivityPub.contain_timeline(user) |> ActivityPub.contain_timeline(user)
|> Enum.reverse() |> Enum.reverse()
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> add_link_headers(:home_timeline, activities) |> add_link_headers(:home_timeline, activities)
|> put_view(StatusView) |> put_view(StatusView)
@ -297,6 +305,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|> ActivityPub.fetch_public_activities() |> ActivityPub.fetch_public_activities()
|> Enum.reverse() |> Enum.reverse()
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> add_link_headers(:public_timeline, activities, false, %{"local" => local_only}) |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
|> put_view(StatusView) |> put_view(StatusView)
@ -304,7 +314,8 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
end end
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
with %User{} = user <- User.get_by_id(params["id"]) do with %User{} = user <- User.get_cached_by_id(params["id"]),
reading_user <- Repo.preload(reading_user, :bookmarks) do
activities = ActivityPub.fetch_user_activities(user, reading_user, params) activities = ActivityPub.fetch_user_activities(user, reading_user, params)
conn conn
@ -331,6 +342,8 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|> ActivityPub.fetch_activities_query(params) |> ActivityPub.fetch_activities_query(params)
|> Pagination.fetch_paginated(params) |> Pagination.fetch_paginated(params)
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> add_link_headers(:dm_timeline, activities) |> add_link_headers(:dm_timeline, activities)
|> put_view(StatusView) |> put_view(StatusView)
@ -340,6 +353,8 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user}) |> try_render("status.json", %{activity: activity, for: user})
@ -489,6 +504,8 @@ def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
%Activity{} = announce <- Activity.normalize(announce.data) do %Activity{} = announce <- Activity.normalize(announce.data) do
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: announce, for: user, as: :activity}) |> try_render("status.json", %{activity: announce, for: user, as: :activity})
@ -498,6 +515,8 @@ def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity}) |> try_render("status.json", %{activity: activity, for: user, as: :activity})
@ -545,10 +564,11 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%Object{} = object <- Object.normalize(activity), %User{} = user <- User.get_cached_by_nickname(user.nickname),
%User{} = user <- User.get_by_nickname(user.nickname),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.bookmark(user, object.data["id"]) do {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity}) |> try_render("status.json", %{activity: activity, for: user, as: :activity})
@ -557,10 +577,11 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%Object{} = object <- Object.normalize(activity), %User{} = user <- User.get_cached_by_nickname(user.nickname),
%User{} = user <- User.get_by_nickname(user.nickname),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.unbookmark(user, object.data["id"]) do {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity}) |> try_render("status.json", %{activity: activity, for: user, as: :activity})
@ -750,7 +771,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
end end
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
with %User{} = user <- User.get_by_id(id), with %User{} = user <- User.get_cached_by_id(id),
followers <- MastodonAPI.get_followers(user, params) do followers <- MastodonAPI.get_followers(user, params) do
followers = followers =
cond do cond do
@ -767,7 +788,7 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
end end
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
with %User{} = user <- User.get_by_id(id), with %User{} = user <- User.get_cached_by_id(id),
followers <- MastodonAPI.get_friends(user, params) do followers <- MastodonAPI.get_friends(user, params) do
followers = followers =
cond do cond do
@ -792,7 +813,7 @@ def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
end end
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- User.get_by_id(id), with %User{} = follower <- User.get_cached_by_id(id),
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
conn conn
|> put_view(AccountView) |> put_view(AccountView)
@ -806,7 +827,7 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}
end end
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- User.get_by_id(id), with %User{} = follower <- User.get_cached_by_id(id),
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
conn conn
|> put_view(AccountView) |> put_view(AccountView)
@ -872,7 +893,7 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
end end
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
with %User{} = muted <- User.get_by_id(id), with %User{} = muted <- User.get_cached_by_id(id),
{:ok, muter} <- User.mute(muter, muted) do {:ok, muter} <- User.mute(muter, muted) do
conn conn
|> put_view(AccountView) |> put_view(AccountView)
@ -886,7 +907,7 @@ def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
end end
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
with %User{} = muted <- User.get_by_id(id), with %User{} = muted <- User.get_cached_by_id(id),
{:ok, muter} <- User.unmute(muter, muted) do {:ok, muter} <- User.unmute(muter, muted) do
conn conn
|> put_view(AccountView) |> put_view(AccountView)
@ -907,7 +928,7 @@ def mutes(%{assigns: %{user: user}} = conn, _) do
end end
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
with %User{} = blocked <- User.get_by_id(id), with %User{} = blocked <- User.get_cached_by_id(id),
{:ok, blocker} <- User.block(blocker, blocked), {:ok, blocker} <- User.block(blocker, blocked),
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
conn conn
@ -922,7 +943,7 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
end end
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
with %User{} = blocked <- User.get_by_id(id), with %User{} = blocked <- User.get_cached_by_id(id),
{:ok, blocker} <- User.unblock(blocker, blocked), {:ok, blocker} <- User.unblock(blocker, blocked),
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
conn conn
@ -1081,21 +1102,65 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
ActivityPub.fetch_activities([], params) ActivityPub.fetch_activities([], params)
|> Enum.reverse() |> Enum.reverse()
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> add_link_headers(:favourites, activities) |> add_link_headers(:favourites, activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json", %{activities: activities, for: user, as: :activity})
end end
def bookmarks(%{assigns: %{user: user}} = conn, _) do def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
user = User.get_by_id(user.id) with %User{} = user <- User.get_by_id(id),
false <- user.info.hide_favorites do
params =
params
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", for_user)
recipients =
if for_user do
["https://www.w3.org/ns/activitystreams#Public"] ++
[for_user.ap_id | for_user.following]
else
["https://www.w3.org/ns/activitystreams#Public"]
end
activities =
recipients
|> ActivityPub.fetch_activities(params)
|> Enum.reverse()
conn
|> add_link_headers(:favourites, activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
else
nil ->
{:error, :not_found}
true ->
conn
|> put_status(403)
|> json(%{error: "Can't get favorites"})
end
end
def bookmarks(%{assigns: %{user: user}} = conn, params) do
user = User.get_cached_by_id(user.id)
user = Repo.preload(user, bookmarks: :activity)
bookmarks =
Bookmark.for_user_query(user.id)
|> Pagination.fetch_paginated(params)
activities = activities =
user.bookmarks bookmarks
|> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end) |> Enum.map(fn b -> b.activity end)
|> Enum.reverse()
conn conn
|> add_link_headers(:bookmarks, bookmarks)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json", %{activities: activities, for: user, as: :activity})
end end
@ -1145,7 +1210,7 @@ def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" =>
accounts accounts
|> Enum.each(fn account_id -> |> Enum.each(fn account_id ->
with %Pleroma.List{} = list <- Pleroma.List.get(id, user), with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
%User{} = followed <- User.get_by_id(account_id) do %User{} = followed <- User.get_cached_by_id(account_id) do
Pleroma.List.follow(list, followed) Pleroma.List.follow(list, followed)
end end
end) end)
@ -1157,7 +1222,7 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id
accounts accounts
|> Enum.each(fn account_id -> |> Enum.each(fn account_id ->
with %Pleroma.List{} = list <- Pleroma.List.get(id, user), with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
%User{} = followed <- Pleroma.User.get_by_id(account_id) do %User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do
Pleroma.List.unfollow(list, followed) Pleroma.List.unfollow(list, followed)
end end
end) end)
@ -1201,6 +1266,8 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|> ActivityPub.fetch_activities_bounded(following, params) |> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse() |> Enum.reverse()
user = Repo.preload(user, bookmarks: :activity)
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json", %{activities: activities, for: user, as: :activity})
@ -1450,7 +1517,7 @@ def logout(conn, _) do
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
Logger.debug("Unimplemented, returning unmodified relationship") Logger.debug("Unimplemented, returning unmodified relationship")
with %User{} = target <- User.get_by_id(id) do with %User{} = target <- User.get_cached_by_id(id) do
conn conn
|> put_view(AccountView) |> put_view(AccountView)
|> render("relationship.json", %{user: user, target: target}) |> render("relationship.json", %{user: user, target: target})

View File

@ -68,7 +68,7 @@ def render("relationships.json", %{user: user, targets: targets}) do
defp do_render("account.json", %{user: user} = opts) do defp do_render("account.json", %{user: user} = opts) do
image = User.avatar_url(user) |> MediaProxy.url() image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url() header = User.banner_url(user) |> MediaProxy.url()
user_info = User.user_info(user) user_info = User.get_cached_user_info(user)
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"] bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
emojis = emojis =
@ -113,21 +113,23 @@ defp do_render("account.json", %{user: user} = opts) do
bot: bot, bot: bot,
source: %{ source: %{
note: "", note: "",
privacy: user_info.default_scope, sensitive: false,
sensitive: false pleroma: %{}
}, },
# Pleroma extension # Pleroma extension
pleroma: pleroma: %{
%{ confirmation_pending: user_info.confirmation_pending,
confirmation_pending: user_info.confirmation_pending, tags: user.tags,
tags: user.tags, hide_followers: user.info.hide_followers,
is_moderator: user.info.is_moderator, hide_follows: user.info.hide_follows,
is_admin: user.info.is_admin, hide_favorites: user.info.hide_favorites,
relationship: relationship relationship: relationship
} }
|> with_notification_settings(user, opts[:for])
} }
|> maybe_put_role(user, opts[:for])
|> maybe_put_settings(user, opts[:for], user_info)
|> maybe_put_notification_settings(user, opts[:for])
end end
defp username_from_nickname(string) when is_binary(string) do defp username_from_nickname(string) when is_binary(string) do
@ -136,9 +138,37 @@ defp username_from_nickname(string) when is_binary(string) do
defp username_from_nickname(_), do: nil defp username_from_nickname(_), do: nil
defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do defp maybe_put_settings(
Map.put(data, :notification_settings, user.info.notification_settings) data,
%User{id: user_id} = user,
%User{id: user_id},
user_info
) do
data
|> Kernel.put_in([:source, :privacy], user_info.default_scope)
|> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role)
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text)
end end
defp with_notification_settings(data, _, _), do: data defp maybe_put_settings(data, _, _, _), do: data
defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
data
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
end
defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
data
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
end
defp maybe_put_role(data, _, _), do: data
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)
end
defp maybe_put_notification_settings(data, _, _), do: data
end end

View File

@ -31,7 +31,7 @@ defp get_replied_to_activities(activities) do
|> Activity.create_by_object_ap_id() |> Activity.create_by_object_ap_id()
|> Repo.all() |> Repo.all()
|> Enum.reduce(%{}, fn activity, acc -> |> Enum.reduce(%{}, fn activity, acc ->
object = Object.normalize(activity.data["object"]) object = Object.normalize(activity)
Map.put(acc, object.data["id"], activity) Map.put(acc, object.data["id"], activity)
end) end)
end end
@ -85,7 +85,8 @@ def render(
activity_object = Object.normalize(activity) activity_object = Object.normalize(activity)
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
bookmarked = opts[:for] && activity_object.data["id"] in opts[:for].bookmarks
bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity)
mentions = mentions =
activity.recipients activity.recipients
@ -148,7 +149,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
bookmarked = opts[:for] && object.data["id"] in opts[:for].bookmarks bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity)
attachment_data = object.data["attachment"] || [] attachment_data = object.data["attachment"] || []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
@ -238,6 +239,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
pleroma: %{ pleroma: %{
local: activity.local, local: activity.local,
conversation_id: get_context_id(activity), conversation_id: get_context_id(activity),
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext}, content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary_plaintext} spoiler_text: %{"text/plain" => summary_plaintext}
} }
@ -316,7 +318,7 @@ def render("attachment.json", %{attachment: attachment}) do
end end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
object = Object.normalize(activity.data["object"]) object = Object.normalize(activity)
with nil <- replied_to_activities[object.data["inReplyTo"]] do with nil <- replied_to_activities[object.data["inReplyTo"]] do
# If user didn't participate in the thread # If user didn't participate in the thread

View File

@ -90,7 +90,7 @@ defp allow_request(stream, nil) when stream in @anonymous_streams do
# Authenticated streams. # Authenticated streams.
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
user = %User{} <- User.get_by_id(user_id) do user = %User{} <- User.get_cached_by_id(user_id) do
{:ok, user} {:ok, user}
else else
_ -> {:error, 403} _ -> {:error, 403}

View File

@ -13,32 +13,44 @@ def url("/" <> _ = url), do: url
def url(url) do def url(url) do
config = Application.get_env(:pleroma, :media_proxy, []) config = Application.get_env(:pleroma, :media_proxy, [])
domain = URI.parse(url).host
if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) do cond do
url !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) ->
else
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
# Must preserve `%2F` for compatibility with S3
# https://git.pleroma.social/pleroma/pleroma/issues/580
replacement = get_replacement(url, ":2F:")
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
base64 =
url url
|> String.replace("%2F", replacement)
|> URI.decode()
|> URI.encode()
|> String.replace(replacement, "%2F")
|> Base.url_encode64(@base64_opts)
sig = :crypto.hmac(:sha, secret, base64) Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
sig64 = sig |> Base.url_encode64(@base64_opts) String.equivalent?(domain, pattern)
end) ->
url
build_url(sig64, base64, filename(url)) true ->
encode_url(url)
end end
end end
def encode_url(url) do
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
# Must preserve `%2F` for compatibility with S3
# https://git.pleroma.social/pleroma/pleroma/issues/580
replacement = get_replacement(url, ":2F:")
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
base64 =
url
|> String.replace("%2F", replacement)
|> URI.decode()
|> URI.encode()
|> String.replace(replacement, "%2F")
|> Base.url_encode64(@base64_opts)
sig = :crypto.hmac(:sha, secret, base64)
sig64 = sig |> Base.url_encode64(@base64_opts)
build_url(sig64, base64, filename(url))
end
def decode_url(sig, url) do def decode_url(sig, url) do
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
sig = Base.url_decode64!(sig, @base64_opts) sig = Base.url_decode64!(sig, @base64_opts)

View File

@ -143,7 +143,7 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
fixed_token = fix_padding(params["code"]), fixed_token = fix_padding(params["code"]),
%Authorization{} = auth <- %Authorization{} = auth <-
Repo.get_by(Authorization, token: fixed_token, app_id: app.id), Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
%User{} = user <- User.get_by_id(auth.user_id), %User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth), {:ok, token} <- Token.exchange_token(app, auth),
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
response = %{ response = %{

View File

@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do
def exchange_token(app, auth) do def exchange_token(app, auth) do
with {:ok, auth} <- Authorization.use_token(auth), with {:ok, auth} <- Authorization.use_token(auth),
true <- auth.app_id == app.id do true <- auth.app_id == app.id do
create_token(app, User.get_by_id(auth.user_id), auth.scopes) create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes)
end end
end end

View File

@ -84,7 +84,7 @@ def to_simple_form(activity, user, with_author \\ false)
def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do
h = fn str -> [to_charlist(str)] end h = fn str -> [to_charlist(str)] end
object = Object.normalize(activity.data["object"]) object = Object.normalize(activity)
updated_at = object.data["published"] updated_at = object.data["published"]
inserted_at = object.data["published"] inserted_at = object.data["published"]

View File

@ -294,7 +294,7 @@ def make_user(uri, update \\ false) do
} }
with false <- update, with false <- update,
%User{} = user <- User.get_by_ap_id(data.ap_id) do %User{} = user <- User.get_cached_by_ap_id(data.ap_id) do
{:ok, user} {:ok, user}
else else
_e -> User.insert_or_update_user(data) _e -> User.insert_or_update_user(data)

View File

@ -21,8 +21,10 @@ defmodule Pleroma.Web.Push.Impl do
@doc "Performs sending notifications for user subscriptions" @doc "Performs sending notifications for user subscriptions"
@spec perform(Notification.t()) :: list(any) | :error @spec perform(Notification.t()) :: list(any) | :error
def perform( def perform(
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} = %{
notif activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
user_id: user_id
} = notif
) )
when activity_type in @types do when activity_type in @types do
actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
@ -30,13 +32,14 @@ def perform(
type = Activity.mastodon_notification_type(notif.activity) type = Activity.mastodon_notification_type(notif.activity)
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key) gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor) avatar_url = User.avatar_url(actor)
object = Object.normalize(activity)
for subscription <- fetch_subsriptions(user_id), for subscription <- fetch_subsriptions(user_id),
get_in(subscription.data, ["alerts", type]) do get_in(subscription.data, ["alerts", type]) do
%{ %{
title: format_title(notif), title: format_title(notif),
access_token: subscription.token.token, access_token: subscription.token.token,
body: format_body(notif, actor), body: format_body(notif, actor, object),
notification_id: notif.id, notification_id: notif.id,
notification_type: type, notification_type: type,
icon: avatar_url, icon: avatar_url,
@ -95,25 +98,25 @@ def build_sub(subscription) do
end end
def format_body( def format_body(
%{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}}, %{activity: %{data: %{"type" => "Create"}}},
actor actor,
%{data: %{"content" => content}}
) do ) do
"@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}" "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
end end
def format_body( def format_body(
%{activity: %{data: %{"type" => "Announce", "object" => activity_id}}}, %{activity: %{data: %{"type" => "Announce"}}},
actor actor,
%{data: %{"content" => content}}
) do ) do
%Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id)
%Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id)
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}" "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
end end
def format_body( def format_body(
%{activity: %{data: %{"type" => type}}}, %{activity: %{data: %{"type" => type}}},
actor actor,
_object
) )
when type in ["Follow", "Like"] do when type in ["Follow", "Like"] do
case type do case type do

View File

@ -135,6 +135,7 @@ defmodule Pleroma.Web.Router do
post("/password_reset", UtilController, :password_reset) post("/password_reset", UtilController, :password_reset)
get("/emoji", UtilController, :emoji) get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha) get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck)
end end
scope "/api/pleroma", Pleroma.Web do scope "/api/pleroma", Pleroma.Web do
@ -394,6 +395,8 @@ defmodule Pleroma.Web.Router do
get("/accounts/:id", MastodonAPIController, :user) get("/accounts/:id", MastodonAPIController, :user)
get("/search", MastodonAPIController, :search) get("/search", MastodonAPIController, :search)
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
end end
end end

View File

@ -81,7 +81,7 @@ def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
_ -> _ ->
Pleroma.List.get_lists_from_activity(item) Pleroma.List.get_lists_from_activity(item)
|> Enum.filter(fn list -> |> Enum.filter(fn list ->
owner = User.get_by_id(list.user_id) owner = User.get_cached_by_id(list.user_id)
Visibility.visible_for_user?(item, owner) Visibility.visible_for_user?(item, owner)
end) end)

View File

@ -22,7 +22,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
def show_password_reset(conn, %{"token" => token}) do def show_password_reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
%User{} = user <- User.get_by_id(token.user_id) do %User{} = user <- User.get_cached_by_id(token.user_id) do
render(conn, "password_reset.html", %{ render(conn, "password_reset.html", %{
token: token, token: token,
user: user user: user
@ -113,13 +113,13 @@ defp is_status?(acct) do
def do_remote_follow(conn, %{ def do_remote_follow(conn, %{
"authorization" => %{"name" => username, "password" => password, "id" => id} "authorization" => %{"name" => username, "password" => password, "id" => id}
}) do }) do
followee = User.get_by_id(id) followee = User.get_cached_by_id(id)
avatar = User.avatar_url(followee) avatar = User.avatar_url(followee)
name = followee.nickname name = followee.nickname
with %User{} = user <- User.get_cached_by_nickname(username), with %User{} = user <- User.get_cached_by_nickname(username),
true <- Pbkdf2.checkpw(password, user.password_hash), true <- Pbkdf2.checkpw(password, user.password_hash),
%User{} = _followed <- User.get_by_id(id), %User{} = _followed <- User.get_cached_by_id(id),
{:ok, follower} <- User.follow(user, followee), {:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do {:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn conn
@ -141,7 +141,7 @@ def do_remote_follow(conn, %{
end end
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
with %User{} = followee <- User.get_by_id(id), with %User{} = followee <- User.get_cached_by_id(id),
{:ok, follower} <- User.follow(user, followee), {:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do {:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn conn
@ -363,4 +363,22 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
def captcha(conn, _params) do def captcha(conn, _params) do
json(conn, Pleroma.Captcha.new()) json(conn, Pleroma.Captcha.new())
end end
def healthcheck(conn, _params) do
info =
if Pleroma.Config.get([:instance, :healthcheck]) do
Pleroma.Healthcheck.system_info()
else
%{}
end
conn =
if info[:healthy] do
conn
else
Plug.Conn.put_status(conn, :service_unavailable)
end
json(conn, info)
end
end end

View File

@ -240,7 +240,7 @@ def get_user(user \\ nil, params) do
end end
%{"screen_name" => nickname} -> %{"screen_name" => nickname} ->
case User.get_by_nickname(nickname) do case User.get_cached_by_nickname(nickname) do
nil -> {:error, "No user with such screen_name"} nil -> {:error, "No user with such screen_name"}
target -> {:ok, target} target -> {:ok, target}
end end

View File

@ -434,7 +434,7 @@ def password_reset(conn, params) do
end end
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_by_id(uid), with %User{} = user <- User.get_cached_by_id(uid),
true <- user.local, true <- user.local,
true <- user.info.confirmation_pending, true <- user.info.confirmation_pending,
true <- user.info.confirmation_token == token, true <- user.info.confirmation_token == token,
@ -587,7 +587,7 @@ def friend_requests(conn, params) do
def approve_friend_request(conn, %{"user_id" => uid} = _params) do def approve_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user], with followed <- conn.assigns[:user],
%User{} = follower <- User.get_by_id(uid), %User{} = follower <- User.get_cached_by_id(uid),
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
conn conn
|> put_view(UserView) |> put_view(UserView)
@ -599,7 +599,7 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do
def deny_friend_request(conn, %{"user_id" => uid} = _params) do def deny_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user], with followed <- conn.assigns[:user],
%User{} = follower <- User.get_by_id(uid), %User{} = follower <- User.get_cached_by_id(uid),
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
conn conn
|> put_view(UserView) |> put_view(UserView)
@ -632,7 +632,7 @@ def raw_empty_array(conn, _params) do
defp build_info_cng(user, params) do defp build_info_cng(user, params) do
info_params = info_params =
["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"] ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
|> Enum.reduce(%{}, fn key, res -> |> Enum.reduce(%{}, fn key, res ->
if value = params[key] do if value = params[key] do
Map.put(res, key, value == "true") Map.put(res, key, value == "true")

View File

@ -74,58 +74,49 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
data = %{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
"description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,
"follows_you" => follows_you,
"statusnet_blocking" => statusnet_blocking,
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name || user.nickname,
"name_html" =>
if(user.name,
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
else: user.nickname
),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"rights" => %{
"delete_others_notice" => !!user.info.is_moderator,
"admin" => !!user.info.is_admin
},
"screen_name" => user.nickname,
"statuses_count" => user_info[:note_count],
"statusnet_profile_url" => user.ap_id,
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
"is_local" => user.local,
"locked" => user.info.locked,
"default_scope" => user.info.default_scope,
"no_rich_text" => user.info.no_rich_text,
"hide_followers" => user.info.hide_followers,
"hide_follows" => user.info.hide_follows,
"fields" => fields,
# Pleroma extension
"pleroma" =>
%{
"confirmation_pending" => user_info.confirmation_pending,
"tags" => user.tags
}
|> maybe_with_activation_status(user, for_user)
}
data = data =
if(user.info.is_admin || user.info.is_moderator, %{
do: maybe_with_role(data, user, for_user), "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
else: data "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
) "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,
"follows_you" => follows_you,
"statusnet_blocking" => statusnet_blocking,
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name || user.nickname,
"name_html" =>
if(user.name,
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
else: user.nickname
),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"screen_name" => user.nickname,
"statuses_count" => user_info[:note_count],
"statusnet_profile_url" => user.ap_id,
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
"is_local" => user.local,
"locked" => user.info.locked,
"hide_followers" => user.info.hide_followers,
"hide_follows" => user.info.hide_follows,
"fields" => fields,
# Pleroma extension
"pleroma" =>
%{
"confirmation_pending" => user_info.confirmation_pending,
"tags" => user.tags
}
|> maybe_with_activation_status(user, for_user)
}
|> maybe_with_user_settings(user, for_user)
|> maybe_with_role(user, for_user)
if assigns[:token] do if assigns[:token] do
Map.put(data, "token", token_string(assigns[:token])) Map.put(data, "token", token_string(assigns[:token]))
@ -141,15 +132,35 @@ defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
defp maybe_with_activation_status(data, _, _), do: data defp maybe_with_activation_status(data, _, _), do: data
defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role}) Map.merge(data, %{
"role" => role(user),
"show_role" => user.info.show_role,
"rights" => %{
"delete_others_notice" => !!user.info.is_moderator,
"admin" => !!user.info.is_admin
}
})
end end
defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
Map.merge(data, %{"role" => role(user)}) Map.merge(data, %{
"role" => role(user),
"rights" => %{
"delete_others_notice" => !!user.info.is_moderator,
"admin" => !!user.info.is_admin
}
})
end end
defp maybe_with_role(data, _, _), do: data defp maybe_with_role(data, _, _), do: data
defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do
data
|> Kernel.put_in(["default_scope"], info.default_scope)
|> Kernel.put_in(["no_rich_text"], info.no_rich_text)
end
defp maybe_with_user_settings(data, _, _), do: data
defp role(%User{info: %{:is_admin => true}}), do: "admin" defp role(%User{info: %{:is_admin => true}}), do: "admin"
defp role(%User{info: %{:is_moderator => true}}), do: "moderator" defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
defp role(_), do: "member" defp role(_), do: "member"

View File

@ -37,7 +37,7 @@ def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
regex = ~r/(acct:)?(?<username>\w+)@#{host}/ regex = ~r/(acct:)?(?<username>\w+)@#{host}/
with %{"username" => username} <- Regex.named_captures(regex, resource), with %{"username" => username} <- Regex.named_captures(regex, resource),
%User{} = user <- User.get_by_nickname(username) do %User{} = user <- User.get_cached_by_nickname(username) do
{:ok, represent_user(user, fmt)} {:ok, represent_user(user, fmt)}
else else
_e -> _e ->

View File

@ -84,6 +84,7 @@ defp deps do
{:ex_aws, "~> 2.0"}, {:ex_aws, "~> 2.0"},
{:ex_aws_s3, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"},
{:earmark, "~> 1.3"}, {:earmark, "~> 1.3"},
{:bbcode, "~> 0.1"},
{:ex_machina, "~> 2.3", only: :test}, {:ex_machina, "~> 2.3", only: :test},
{:credo, "~> 0.9.3", only: [:dev, :test]}, {:credo, "~> 0.9.3", only: [:dev, :test]},
{:mock, "~> 0.3.1", only: :test}, {:mock, "~> 0.3.1", only: :test},

View File

@ -2,6 +2,7 @@
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"}, "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "90613b4bae875a3610c275b7056b61ffdd53210d", [ref: "90613b4bae875a3610c275b7056b61ffdd53210d"]}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "90613b4bae875a3610c275b7056b61ffdd53210d", [ref: "90613b4bae875a3610c275b7056b61ffdd53210d"]},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:hex, :bbcode, "0.1.0", "400e618b640b635261611d7fb7f79d104917fc5b084aae371ab6b08477cb035b", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"}, "cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
"calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -0,0 +1,14 @@
defmodule Pleroma.Repo.Migrations.CreateBookmarks do
use Ecto.Migration
def change do
create table(:bookmarks) do
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all))
timestamps()
end
create(unique_index(:bookmarks, [:user_id, :activity_id]))
end
end

View File

@ -0,0 +1,29 @@
defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
use Ecto.Migration
import Ecto.Query
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.User
alias Pleroma.Repo
def change do
query =
from(u in User,
where: u.local == true,
where: fragment("array_length(bookmarks, 1)") > 0,
select: %{id: u.id, bookmarks: fragment("bookmarks")}
)
Repo.stream(query)
|> Enum.each(fn %{id: user_id, bookmarks: bookmarks} ->
Enum.each(bookmarks, fn ap_id ->
activity = Activity.get_create_by_object_ap_id(ap_id)
unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
end)
end)
alter table(:users) do
remove(:bookmarks)
end
end
end

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.ea66966b753e709d7ce58f910a2c003e.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.0b2f423dda42f0dbbf65.js></script><script type=text/javascript src=/static/js/vendor.e4475fde034685231799.js></script><script type=text/javascript src=/static/js/app.77434de4e756a5d79672.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.a81578273cb4c57163939ab70c80eb06.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.bf15f24d205c8cf4ee4a.js></script><script type=text/javascript src=/static/js/vendor.0d1eeaf25aa1d2fc51b0.js></script><script type=text/javascript src=/static/js/app.c914d9a57d5da7aa5553.js></script></body></html>

View File

@ -8,7 +8,6 @@
"redirectRootLogin": "/main/friends", "redirectRootLogin": "/main/friends",
"chatDisabled": false, "chatDisabled": false,
"showInstanceSpecificPanel": false, "showInstanceSpecificPanel": false,
"scopeOptionsEnabled": false,
"formattingOptionsEnabled": false, "formattingOptionsEnabled": false,
"collapseMessageWithSubject": false, "collapseMessageWithSubject": false,
"scopeCopy": true, "scopeCopy": true,
@ -21,5 +20,6 @@
"webPushNotifications": false, "webPushNotifications": false,
"noAttachmentLinks": false, "noAttachmentLinks": false,
"nsfwCensorImage": "", "nsfwCensorImage": "",
"showFeaturesPanel": true "showFeaturesPanel": true,
"minimalScopesMode": false
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

0
priv/static/static/font/LICENSE.txt Executable file → Normal file
View File

0
priv/static/static/font/README.txt Executable file → Normal file
View File

12
priv/static/static/font/config.json Executable file → Normal file
View File

@ -239,6 +239,18 @@
"css": "pencil", "css": "pencil",
"code": 59416, "code": 59416,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "671f29fa10dda08074a4c6a341bb4f39",
"css": "bell-alt",
"code": 61683,
"src": "fontawesome"
},
{
"uid": "5bb103cd29de77e0e06a52638527b575",
"css": "wrench",
"code": 59418,
"src": "fontawesome"
} }
] ]
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -229,11 +229,11 @@ body {
} }
@font-face { @font-face {
font-family: 'fontello'; font-family: 'fontello';
src: url('./font/fontello.eot?50378338'); src: url('./font/fontello.eot?60799712');
src: url('./font/fontello.eot?50378338#iefix') format('embedded-opentype'), src: url('./font/fontello.eot?60799712#iefix') format('embedded-opentype'),
url('./font/fontello.woff?50378338') format('woff'), url('./font/fontello.woff?60799712') format('woff'),
url('./font/fontello.ttf?50378338') format('truetype'), url('./font/fontello.ttf?60799712') format('truetype'),
url('./font/fontello.svg?50378338#fontello') format('svg'); url('./font/fontello.svg?60799712#fontello') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -335,24 +335,29 @@ body {
</div> </div>
<div class="row"> <div class="row">
<div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-pencil">&#xe818;</i> <span class="i-name">icon-pencil</span><span class="i-code">0xe818</span></div> <div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-pencil">&#xe818;</i> <span class="i-name">icon-pencil</span><span class="i-code">0xe818</span></div>
<div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-verified">&#xe819;</i> <span class="i-name">icon-verified</span><span class="i-code">0xe819</span></div>
<div class="the-icons span3" title="Code: 0xe81a"><i class="demo-icon icon-wrench">&#xe81a;</i> <span class="i-name">icon-wrench</span><span class="i-code">0xe81a</span></div>
<div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div> <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-spin3 animate-spin">&#xe832;</i> <span class="i-name">icon-spin3</span><span class="i-code">0xe832</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div> <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-spin4 animate-spin">&#xe834;</i> <span class="i-name">icon-spin4</span><span class="i-code">0xe834</span></div>
<div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div> <div class="the-icons span3" title="Code: 0xf08e"><i class="demo-icon icon-link-ext">&#xf08e;</i> <span class="i-name">icon-link-ext</span><span class="i-code">0xf08e</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div> <div class="the-icons span3" title="Code: 0xf08f"><i class="demo-icon icon-link-ext-alt">&#xf08f;</i> <span class="i-name">icon-link-ext-alt</span><span class="i-code">0xf08f</span></div>
<div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div> <div class="the-icons span3" title="Code: 0xf0c9"><i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span></div>
<div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
<div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
</div> </div>
<div class="row"> <div class="row">
<div class="the-icons span3" title="Code: 0xf0e0"><i class="demo-icon icon-mail-alt">&#xf0e0;</i> <span class="i-name">icon-mail-alt</span><span class="i-code">0xf0e0</span></div>
<div class="the-icons span3" title="Code: 0xf0e5"><i class="demo-icon icon-comment-empty">&#xf0e5;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xf0e5</span></div>
<div class="the-icons span3" title="Code: 0xf0f3"><i class="demo-icon icon-bell-alt">&#xf0f3;</i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xf0f3</span></div>
<div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div> <div class="the-icons span3" title="Code: 0xf0fe"><i class="demo-icon icon-plus-squared">&#xf0fe;</i> <span class="i-name">icon-plus-squared</span><span class="i-code">0xf0fe</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div> <div class="the-icons span3" title="Code: 0xf112"><i class="demo-icon icon-reply">&#xf112;</i> <span class="i-name">icon-reply</span><span class="i-code">0xf112</span></div>
<div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div> <div class="the-icons span3" title="Code: 0xf13e"><i class="demo-icon icon-lock-open-alt">&#xf13e;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xf13e</span></div>
<div class="the-icons span3" title="Code: 0xf144"><i class="demo-icon icon-play-circled">&#xf144;</i> <span class="i-name">icon-play-circled</span><span class="i-code">0xf144</span></div> <div class="the-icons span3" title="Code: 0xf144"><i class="demo-icon icon-play-circled">&#xf144;</i> <span class="i-name">icon-play-circled</span><span class="i-code">0xf144</span></div>
<div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt">&#xf164;</i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
</div> </div>
<div class="row"> <div class="row">
<div class="the-icons span3" title="Code: 0xf164"><i class="demo-icon icon-thumbs-up-alt">&#xf164;</i> <span class="i-name">icon-thumbs-up-alt</span><span class="i-code">0xf164</span></div>
<div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div> <div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
<div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div> <div class="the-icons span3" title="Code: 0xf234"><i class="demo-icon icon-user-plus">&#xf234;</i> <span class="i-name">icon-user-plus</span><span class="i-code">0xf234</span></div>
</div> </div>

View File

@ -56,6 +56,10 @@
<glyph glyph-name="pencil" unicode="&#xe818;" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" /> <glyph glyph-name="pencil" unicode="&#xe818;" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
<glyph glyph-name="verified" unicode="&#xe819;" d="M926 453l-19 13c-21 14-30 41-23 65l6 22c10 34-13 69-48 75l-23 4c-25 4-45 23-49 48l-4 23c-6 35-41 57-75 47l-22-7c-24-7-51 2-65 22l-14 20c-21 29-62 33-88 9l-17-16c-19-17-46-21-69-8l-20 11c-31 17-70 3-84-30l-9-22c-9-24-33-39-58-37l-23 1c-36 2-65-28-62-63l2-23c2-25-13-49-36-59l-21-9c-33-14-46-53-29-84l12-20c13-22 10-50-7-69l-15-17c-24-27-19-68 11-88l19-13c21-14 30-41 23-65l-9-23c-10-34 13-69 48-75l23-4c25-4 45-23 49-48l4-23c6-35 41-57 75-47l22 7c24 7 51-2 65-22l14-19c21-29 62-33 88-9l17 16c19 17 46 21 69 8l20-11c31-17 70-3 84 30l9 22c9 24 33 39 58 37l23-1c36-2 65 28 62 63l-1 23c-2 25 13 49 36 59l21 9c33 14 46 53 29 84l-12 20c-13 22-10 50 7 69l15 17c25 26 20 68-9 88z m-399-189l-82-81-81 82-78 79 82 81 78-79 187 186 81-82-187-186z" horiz-adv-x="1000" />
<glyph glyph-name="wrench" unicode="&#xe81a;" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
<glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" /> <glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
<glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" /> <glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
@ -70,6 +74,8 @@
<glyph glyph-name="comment-empty" unicode="&#xf0e5;" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" /> <glyph glyph-name="comment-empty" unicode="&#xf0e5;" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
<glyph glyph-name="bell-alt" unicode="&#xf0f3;" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
<glyph glyph-name="plus-squared" unicode="&#xf0fe;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" /> <glyph glyph-name="plus-squared" unicode="&#xf0fe;" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
<glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" /> <glyph glyph-name="reply" unicode="&#xf112;" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

52
test/bookmark_test.exs Normal file
View File

@ -0,0 +1,52 @@
defmodule Pleroma.BookmarkTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.Bookmark
alias Pleroma.Web.CommonAPI
describe "create/2" do
test "with valid params" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Some cool information"})
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
assert bookmark.user_id == user.id
assert bookmark.activity_id == activity.id
end
test "with invalid params" do
{:error, changeset} = Bookmark.create(nil, "")
refute changeset.valid?
assert changeset.errors == [
user_id: {"can't be blank", [validation: :required]},
activity_id: {"can't be blank", [validation: :required]}
]
end
end
describe "destroy/2" do
test "with valid params" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Some cool information"})
{:ok, _bookmark} = Bookmark.create(user.id, activity.id)
{:ok, _deleted_bookmark} = Bookmark.destroy(user.id, activity.id)
end
end
describe "get/2" do
test "gets a bookmark" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" =>
"Scientists Discover The Secret Behind Tenshi Eating A Corndog Being So Cute Science Daily"
})
{:ok, bookmark} = Bookmark.create(user.id, activity.id)
assert bookmark == Bookmark.get(user.id, activity.id)
end
end
end

22
test/healthcheck_test.exs Normal file
View File

@ -0,0 +1,22 @@
defmodule Pleroma.HealthcheckTest do
use Pleroma.DataCase
alias Pleroma.Healthcheck
test "system_info/0" do
result = Healthcheck.system_info() |> Map.from_struct()
assert Map.keys(result) == [:active, :healthy, :idle, :memory_used, :pool_size]
end
describe "check_health/1" do
test "pool size equals active connections" do
result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 10})
refute result.healthy
end
test "chech_health/1" do
result = Healthcheck.check_health(%Healthcheck{pool_size: 10, active: 9})
assert result.healthy
end
end
end

View File

@ -20,6 +20,18 @@ defmodule Pleroma.HTMLTest do
<img src="http://example.com/image.jpg" onerror="alert('hacked')"> <img src="http://example.com/image.jpg" onerror="alert('hacked')">
""" """
@html_span_class_sample """
<span class="animate-spin">hi</span>
"""
@html_span_microformats_sample """
<span class="h-card"><a class="u-url mention">@<span>foo</span></a></span>
"""
@html_span_invalid_microformats_sample """
<span class="h-card"><a class="u-url mention animate-spin">@<span>foo</span></a></span>
"""
describe "StripTags scrubber" do describe "StripTags scrubber" do
test "works as expected" do test "works as expected" do
expected = """ expected = """
@ -64,6 +76,36 @@ test "does not allow attribute-based XSS" do
assert expected == HTML.filter_tags(@html_onerror_sample, Pleroma.HTML.Scrubber.TwitterText) assert expected == HTML.filter_tags(@html_onerror_sample, Pleroma.HTML.Scrubber.TwitterText)
end end
test "does not allow spans with invalid classes" do
expected = """
<span>hi</span>
"""
assert expected ==
HTML.filter_tags(@html_span_class_sample, Pleroma.HTML.Scrubber.TwitterText)
end
test "does allow microformats" do
expected = """
<span class="h-card"><a class="u-url mention">@<span>foo</span></a></span>
"""
assert expected ==
HTML.filter_tags(@html_span_microformats_sample, Pleroma.HTML.Scrubber.TwitterText)
end
test "filters invalid microformats markup" do
expected = """
<span class="h-card"><a>@<span>foo</span></a></span>
"""
assert expected ==
HTML.filter_tags(
@html_span_invalid_microformats_sample,
Pleroma.HTML.Scrubber.TwitterText
)
end
end end
describe "default scrubber" do describe "default scrubber" do
@ -88,5 +130,34 @@ test "does not allow attribute-based XSS" do
assert expected == HTML.filter_tags(@html_onerror_sample, Pleroma.HTML.Scrubber.Default) assert expected == HTML.filter_tags(@html_onerror_sample, Pleroma.HTML.Scrubber.Default)
end end
test "does not allow spans with invalid classes" do
expected = """
<span>hi</span>
"""
assert expected == HTML.filter_tags(@html_span_class_sample, Pleroma.HTML.Scrubber.Default)
end
test "does allow microformats" do
expected = """
<span class="h-card"><a class="u-url mention">@<span>foo</span></a></span>
"""
assert expected ==
HTML.filter_tags(@html_span_microformats_sample, Pleroma.HTML.Scrubber.Default)
end
test "filters invalid microformats markup" do
expected = """
<span class="h-card"><a>@<span>foo</span></a></span>
"""
assert expected ==
HTML.filter_tags(
@html_span_invalid_microformats_sample,
Pleroma.HTML.Scrubber.Default
)
end
end end
end end

View File

@ -177,4 +177,13 @@ defp decode_result(encoded) do
{:ok, decoded} = decode_url(sig, base64) {:ok, decoded} = decode_url(sig, base64)
decoded decoded
end end
test "mediaproxy whitelist" do
Pleroma.Config.put([:media_proxy, :enabled], true)
Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"])
url = "https://feld.me/foo.png"
unencoded = url(url)
assert unencoded == url
end
end end

View File

@ -46,7 +46,7 @@ test "it creates a notification for subscribed users" do
describe "create_notification" do describe "create_notification" do
test "it doesn't create a notification for user if the user blocks the activity author" do test "it doesn't create a notification for user if the user blocks the activity author" do
activity = insert(:note_activity) activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"]) author = User.get_cached_by_ap_id(activity.data["actor"])
user = insert(:user) user = insert(:user)
{:ok, user} = User.block(user, author) {:ok, user} = User.block(user, author)
@ -124,7 +124,7 @@ test "it disables notifications from people the user follows" do
test "it doesn't create a notification for user if he is the activity author" do test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity) activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"]) author = User.get_cached_by_ap_id(activity.data["actor"])
assert nil == Notification.create_notification(activity, author) assert nil == Notification.create_notification(activity, author)
end end

View File

@ -31,7 +31,7 @@ test "relay is followed" do
local_user = Relay.get_actor() local_user = Relay.get_actor()
assert local_user.ap_id =~ "/relay" assert local_user.ap_id =~ "/relay"
target_user = User.get_by_ap_id(target_instance) target_user = User.get_cached_by_ap_id(target_instance)
refute target_user.local refute target_user.local
activity = Utils.fetch_latest_follow(local_user, target_user) activity = Utils.fetch_latest_follow(local_user, target_user)
@ -48,7 +48,7 @@ test "relay is unfollowed" do
Mix.Tasks.Pleroma.Relay.run(["follow", target_instance]) Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
%User{ap_id: follower_id} = local_user = Relay.get_actor() %User{ap_id: follower_id} = local_user = Relay.get_actor()
target_user = User.get_by_ap_id(target_instance) target_user = User.get_cached_by_ap_id(target_instance)
follow_activity = Utils.fetch_latest_follow(local_user, target_user) follow_activity = Utils.fetch_latest_follow(local_user, target_user)
Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance]) Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])

View File

@ -50,7 +50,7 @@ test "user is created" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ "created" assert message =~ "created"
user = User.get_by_nickname(unsaved.nickname) user = User.get_cached_by_nickname(unsaved.nickname)
assert user.name == unsaved.name assert user.name == unsaved.name
assert user.email == unsaved.email assert user.email == unsaved.email
assert user.bio == unsaved.bio assert user.bio == unsaved.bio
@ -75,7 +75,7 @@ test "user is not created" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ "will not be created" assert message =~ "will not be created"
refute User.get_by_nickname(unsaved.nickname) refute User.get_cached_by_nickname(unsaved.nickname)
end end
end end
@ -88,7 +88,7 @@ test "user is deleted" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ " deleted" assert message =~ " deleted"
user = User.get_by_nickname(user.nickname) user = User.get_cached_by_nickname(user.nickname)
assert user.info.deactivated assert user.info.deactivated
end end
@ -109,7 +109,7 @@ test "user is deactivated" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ " deactivated" assert message =~ " deactivated"
user = User.get_by_nickname(user.nickname) user = User.get_cached_by_nickname(user.nickname)
assert user.info.deactivated assert user.info.deactivated
end end
@ -121,7 +121,7 @@ test "user is activated" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ " activated" assert message =~ " activated"
user = User.get_by_nickname(user.nickname) user = User.get_cached_by_nickname(user.nickname)
refute user.info.deactivated refute user.info.deactivated
end end
@ -150,7 +150,7 @@ test "user is unsubscribed" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ "Successfully unsubscribed" assert message =~ "Successfully unsubscribed"
user = User.get_by_nickname(user.nickname) user = User.get_cached_by_nickname(user.nickname)
assert Enum.empty?(user.following) assert Enum.empty?(user.following)
assert user.info.deactivated assert user.info.deactivated
end end
@ -178,7 +178,7 @@ test "All statuses set" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ ~r/Admin status .* true/ assert message =~ ~r/Admin status .* true/
user = User.get_by_nickname(user.nickname) user = User.get_cached_by_nickname(user.nickname)
assert user.info.is_moderator assert user.info.is_moderator
assert user.info.locked assert user.info.locked
assert user.info.is_admin assert user.info.is_admin
@ -204,7 +204,7 @@ test "All statuses unset" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ ~r/Admin status .* false/ assert message =~ ~r/Admin status .* false/
user = User.get_by_nickname(user.nickname) user = User.get_cached_by_nickname(user.nickname)
refute user.info.is_moderator refute user.info.is_moderator
refute user.info.locked refute user.info.locked
refute user.info.is_admin refute user.info.is_admin

View File

@ -123,9 +123,9 @@ test "follow takes a user and another user" do
{:ok, user} = User.follow(user, followed) {:ok, user} = User.follow(user, followed)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
followed = User.get_by_ap_id(followed.ap_id) followed = User.get_cached_by_ap_id(followed.ap_id)
assert followed.info.follower_count == 1 assert followed.info.follower_count == 1
assert User.ap_followers(followed) in user.following assert User.ap_followers(followed) in user.following
@ -188,7 +188,7 @@ test "unfollow takes a user and another user" do
{:ok, user, _activity} = User.unfollow(user, followed) {:ok, user, _activity} = User.unfollow(user, followed)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.following == [] assert user.following == []
end end
@ -198,7 +198,7 @@ test "unfollow doesn't unfollow yourself" do
{:error, _} = User.unfollow(user, user) {:error, _} = User.unfollow(user, user)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.following == [user.ap_id] assert user.following == [user.ap_id]
end end
@ -556,8 +556,8 @@ test "gets all friends (followed users) for a given user" do
{:ok, res} = User.get_friends(user) {:ok, res} = User.get_friends(user)
followed_one = User.get_by_ap_id(followed_one.ap_id) followed_one = User.get_cached_by_ap_id(followed_one.ap_id)
followed_two = User.get_by_ap_id(followed_two.ap_id) followed_two = User.get_cached_by_ap_id(followed_two.ap_id)
assert Enum.member?(res, followed_one) assert Enum.member?(res, followed_one)
assert Enum.member?(res, followed_two) assert Enum.member?(res, followed_two)
refute Enum.member?(res, not_followed) refute Enum.member?(res, not_followed)
@ -568,7 +568,7 @@ test "gets all friends (followed users) for a given user" do
test "it sets the info->note_count property" do test "it sets the info->note_count property" do
note = insert(:note) note = insert(:note)
user = User.get_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
assert user.info.note_count == 0 assert user.info.note_count == 0
@ -579,7 +579,7 @@ test "it sets the info->note_count property" do
test "it increases the info->note_count property" do test "it increases the info->note_count property" do
note = insert(:note) note = insert(:note)
user = User.get_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
assert user.info.note_count == 0 assert user.info.note_count == 0
@ -594,7 +594,7 @@ test "it increases the info->note_count property" do
test "it decreases the info->note_count property" do test "it decreases the info->note_count property" do
note = insert(:note) note = insert(:note)
user = User.get_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
assert user.info.note_count == 0 assert user.info.note_count == 0
@ -696,7 +696,7 @@ test "blocks tear down cyclical follow relationships" do
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked) {:ok, blocker} = User.block(blocker, blocked)
blocked = User.get_by_id(blocked.id) blocked = User.get_cached_by_id(blocked.id)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -714,7 +714,7 @@ test "blocks tear down blocker->blocked follow relationships" do
refute User.following?(blocked, blocker) refute User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked) {:ok, blocker} = User.block(blocker, blocked)
blocked = User.get_by_id(blocked.id) blocked = User.get_cached_by_id(blocked.id)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -732,7 +732,7 @@ test "blocks tear down blocked->blocker follow relationships" do
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked) {:ok, blocker} = User.block(blocker, blocked)
blocked = User.get_by_id(blocked.id) blocked = User.get_cached_by_id(blocked.id)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -852,9 +852,9 @@ test ".delete deactivates a user, all follow relationships and all create activi
{:ok, _} = User.delete(user) {:ok, _} = User.delete(user)
followed = User.get_by_id(followed.id) followed = User.get_cached_by_id(followed.id)
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.deactivated assert user.info.deactivated
@ -1008,7 +1008,7 @@ test "works with URIs" do
results = User.search("http://mastodon.example.org/users/admin", resolve: true) results = User.search("http://mastodon.example.org/users/admin", resolve: true)
result = results |> List.first() result = results |> List.first()
user = User.get_by_ap_id("http://mastodon.example.org/users/admin") user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
assert length(results) == 1 assert length(results) == 1
assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil) assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
@ -1125,33 +1125,6 @@ test "Adds rel=me on linkbacked urls" do
end end
end end
test "bookmarks" do
user = insert(:user)
{:ok, activity1} =
CommonAPI.post(user, %{
"status" => "heweoo!"
})
id1 = Object.normalize(activity1).data["id"]
{:ok, activity2} =
CommonAPI.post(user, %{
"status" => "heweoo!"
})
id2 = Object.normalize(activity2).data["id"]
assert {:ok, user_state1} = User.bookmark(user, id1)
assert user_state1.bookmarks == [id1]
assert {:ok, user_state2} = User.unbookmark(user, id1)
assert user_state2.bookmarks == []
assert {:ok, user_state3} = User.bookmark(user, id2)
assert user_state3.bookmarks == [id2]
end
test "follower count is updated when a follower is blocked" do test "follower count is updated when a follower is blocked" do
user = insert(:user) user = insert(:user)
follower = insert(:user) follower = insert(:user)

View File

@ -50,7 +50,7 @@ test "it returns a json representation of the user with accept application/json"
|> put_req_header("accept", "application/json") |> put_req_header("accept", "application/json")
|> get("/users/#{user.nickname}") |> get("/users/#{user.nickname}")
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
end end
@ -65,7 +65,7 @@ test "it returns a json representation of the user with accept application/activ
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}") |> get("/users/#{user.nickname}")
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
end end
@ -83,7 +83,7 @@ test "it returns a json representation of the user with accept application/ld+js
) )
|> get("/users/#{user.nickname}") |> get("/users/#{user.nickname}")
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
end end
@ -572,7 +572,7 @@ test "it works for more than 10 users", %{conn: conn} do
user = insert(:user) user = insert(:user)
Enum.each(1..15, fn _ -> Enum.each(1..15, fn _ ->
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = insert(:user) other_user = insert(:user)
User.follow(user, other_user) User.follow(user, other_user)
end) end)

View File

@ -228,18 +228,30 @@ test "increases user note count only for public activities" do
user = insert(:user) user = insert(:user)
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "1",
"visibility" => "public"
})
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "2",
"visibility" => "unlisted"
})
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "2",
"visibility" => "private"
})
{:ok, _} = {:ok, _} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "3",
"visibility" => "direct"
})
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.note_count == 2 assert user.info.note_count == 2
end end
@ -772,23 +784,35 @@ test "decrements user note count only for public activities" do
user = insert(:user, info: %{note_count: 10}) user = insert(:user, info: %{note_count: 10})
{:ok, a1} = {:ok, a1} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "yeah",
"visibility" => "public"
})
{:ok, a2} = {:ok, a2} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "yeah",
"visibility" => "unlisted"
})
{:ok, a3} = {:ok, a3} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "yeah",
"visibility" => "private"
})
{:ok, a4} = {:ok, a4} =
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"}) CommonAPI.post(User.get_cached_by_id(user.id), %{
"status" => "yeah",
"visibility" => "direct"
})
{:ok, _} = Object.normalize(a1) |> ActivityPub.delete() {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
{:ok, _} = Object.normalize(a2) |> ActivityPub.delete() {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
{:ok, _} = Object.normalize(a3) |> ActivityPub.delete() {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
{:ok, _} = Object.normalize(a4) |> ActivityPub.delete() {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.note_count == 10 assert user.info.note_count == 10
end end

View File

@ -99,7 +99,7 @@ test "it works for incoming notices" do
assert object["sensitive"] == true assert object["sensitive"] == true
user = User.get_by_ap_id(object["actor"]) user = User.get_cached_by_ap_id(object["actor"])
assert user.info.note_count == 1 assert user.info.note_count == 1
end end
@ -212,7 +212,27 @@ test "it works for incoming follow requests" do
assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["actor"] == "http://mastodon.example.org/users/admin"
assert data["type"] == "Follow" assert data["type"] == "Follow"
assert data["id"] == "http://mastodon.example.org/users/admin#follows/2" assert data["id"] == "http://mastodon.example.org/users/admin#follows/2"
assert User.following?(User.get_by_ap_id(data["actor"]), user) assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
end
test "it rejects incoming follow requests from blocked users when deny_follow_blocked is enabled" do
Pleroma.Config.put([:user, :deny_follow_blocked], true)
user = insert(:user)
target = User.get_or_fetch("http://mastodon.example.org/users/admin")
{:ok, user} = User.block(user, target)
data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Poison.decode!()
|> Map.put("object", user.ap_id)
{:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
%Activity{} = activity = Activity.get_by_ap_id(id)
assert activity.data["state"] == "reject"
end end
test "it works for incoming follow requests from hubzilla" do test "it works for incoming follow requests from hubzilla" do
@ -229,7 +249,7 @@ test "it works for incoming follow requests from hubzilla" do
assert data["actor"] == "https://hubzilla.example.org/channel/kaniini" assert data["actor"] == "https://hubzilla.example.org/channel/kaniini"
assert data["type"] == "Follow" assert data["type"] == "Follow"
assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2" assert data["id"] == "https://hubzilla.example.org/channel/kaniini#follows/2"
assert User.following?(User.get_by_ap_id(data["actor"]), user) assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
end end
test "it works for incoming likes" do test "it works for incoming likes" do
@ -540,7 +560,7 @@ test "it works for incomming unfollows with an existing follow" do
assert data["object"]["object"] == user.ap_id assert data["object"]["object"] == user.ap_id
assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["actor"] == "http://mastodon.example.org/users/admin"
refute User.following?(User.get_by_ap_id(data["actor"]), user) refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
end end
test "it works for incoming blocks" do test "it works for incoming blocks" do
@ -557,7 +577,7 @@ test "it works for incoming blocks" do
assert data["object"] == user.ap_id assert data["object"] == user.ap_id
assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["actor"] == "http://mastodon.example.org/users/admin"
blocker = User.get_by_ap_id(data["actor"]) blocker = User.get_cached_by_ap_id(data["actor"])
assert User.blocks?(blocker, user) assert User.blocks?(blocker, user)
end end
@ -584,8 +604,8 @@ test "incoming blocks successfully tear down any follow relationship" do
assert data["object"] == blocked.ap_id assert data["object"] == blocked.ap_id
assert data["actor"] == blocker.ap_id assert data["actor"] == blocker.ap_id
blocker = User.get_by_ap_id(data["actor"]) blocker = User.get_cached_by_ap_id(data["actor"])
blocked = User.get_by_ap_id(data["object"]) blocked = User.get_cached_by_ap_id(data["object"])
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -614,7 +634,7 @@ test "it works for incoming unblocks with an existing block" do
assert data["object"]["object"] == user.ap_id assert data["object"]["object"] == user.ap_id
assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["actor"] == "http://mastodon.example.org/users/admin"
blocker = User.get_by_ap_id(data["actor"]) blocker = User.get_cached_by_ap_id(data["actor"])
refute User.blocks?(blocker, user) refute User.blocks?(blocker, user)
end end
@ -645,7 +665,7 @@ test "it works for incoming accepts which were pre-accepted" do
assert activity.data["object"] == follow_activity.data["id"] assert activity.data["object"] == follow_activity.data["id"]
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, followed) == true assert User.following?(follower, followed) == true
end end
@ -667,7 +687,7 @@ test "it works for incoming accepts which were orphaned" do
{:ok, activity} = Transmogrifier.handle_incoming(accept_data) {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
assert activity.data["object"] == follow_activity.data["id"] assert activity.data["object"] == follow_activity.data["id"]
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, followed) == true assert User.following?(follower, followed) == true
end end
@ -687,7 +707,7 @@ test "it works for incoming accepts which are referenced by IRI only" do
{:ok, activity} = Transmogrifier.handle_incoming(accept_data) {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
assert activity.data["object"] == follow_activity.data["id"] assert activity.data["object"] == follow_activity.data["id"]
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, followed) == true assert User.following?(follower, followed) == true
end end
@ -706,7 +726,7 @@ test "it fails for incoming accepts which cannot be correlated" do
:error = Transmogrifier.handle_incoming(accept_data) :error = Transmogrifier.handle_incoming(accept_data)
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
refute User.following?(follower, followed) == true refute User.following?(follower, followed) == true
end end
@ -725,7 +745,7 @@ test "it fails for incoming rejects which cannot be correlated" do
:error = Transmogrifier.handle_incoming(accept_data) :error = Transmogrifier.handle_incoming(accept_data)
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
refute User.following?(follower, followed) == true refute User.following?(follower, followed) == true
end end
@ -750,7 +770,7 @@ test "it works for incoming rejects which are orphaned" do
{:ok, activity} = Transmogrifier.handle_incoming(reject_data) {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
refute activity.local refute activity.local
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, followed) == false assert User.following?(follower, followed) == false
end end
@ -772,7 +792,7 @@ test "it works for incoming rejects which are referenced by IRI only" do
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data) {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, followed) == false assert User.following?(follower, followed) == false
end end
@ -1026,7 +1046,7 @@ test "it upgrades a user to activitypub" do
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.note_count == 1 assert user.info.note_count == 1
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
@ -1034,7 +1054,7 @@ test "it upgrades a user to activitypub" do
assert user.info.note_count == 1 assert user.info.note_count == 1
assert user.follower_address == "https://niu.moe/users/rye/followers" assert user.follower_address == "https://niu.moe/users/rye/followers"
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.note_count == 1 assert user.info.note_count == 1
activity = Activity.get_by_id(activity.id) activity = Activity.get_by_id(activity.id)
@ -1063,7 +1083,7 @@ test "it upgrades a user to activitypub" do
unrelated_activity = Activity.get_by_id(unrelated_activity.id) unrelated_activity = Activity.get_by_id(unrelated_activity.id)
refute user.follower_address in unrelated_activity.recipients refute user.follower_address in unrelated_activity.recipients
user_two = User.get_by_id(user_two.id) user_two = User.get_cached_by_id(user_two.id)
assert user.follower_address in user_two.following assert user.follower_address in user_two.following
refute "..." in user_two.following refute "..." in user_two.following
end end

View File

@ -1,7 +1,6 @@
defmodule Pleroma.Web.ActivityPub.UtilsTest do defmodule Pleroma.Web.ActivityPub.UtilsTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -12,8 +11,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
describe "fetch the latest Follow" do describe "fetch the latest Follow" do
test "fetches the latest Follow activity" do test "fetches the latest Follow activity" do
%Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
follower = Repo.get_by(User, ap_id: activity.data["actor"]) follower = User.get_cached_by_ap_id(activity.data["actor"])
followed = Repo.get_by(User, ap_id: activity.data["object"]) followed = User.get_cached_by_ap_id(activity.data["object"])
assert activity == Utils.fetch_latest_follow(follower, followed) assert activity == Utils.fetch_latest_follow(follower, followed)
end end

View File

@ -89,8 +89,8 @@ test "allows to force-follow another user" do
"followed" => user.nickname "followed" => user.nickname
}) })
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
assert User.following?(follower, user) assert User.following?(follower, user)
end end
@ -112,8 +112,8 @@ test "allows to force-unfollow another user" do
"followed" => user.nickname "followed" => user.nickname
}) })
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
follower = User.get_by_id(follower.id) follower = User.get_cached_by_id(follower.id)
refute User.following?(follower, user) refute User.following?(follower, user)
end end
@ -145,13 +145,13 @@ test "it appends specified tags to users with specified nicknames", %{
user2: user2 user2: user2
} do } do
assert json_response(conn, :no_content) assert json_response(conn, :no_content)
assert User.get_by_id(user1.id).tags == ["x", "foo", "bar"] assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
assert User.get_by_id(user2.id).tags == ["y", "foo", "bar"] assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
end end
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
assert json_response(conn, :no_content) assert json_response(conn, :no_content)
assert User.get_by_id(user3.id).tags == ["unchanged"] assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
end end
end end
@ -181,13 +181,13 @@ test "it removes specified tags from users with specified nicknames", %{
user2: user2 user2: user2
} do } do
assert json_response(conn, :no_content) assert json_response(conn, :no_content)
assert User.get_by_id(user1.id).tags == [] assert User.get_cached_by_id(user1.id).tags == []
assert User.get_by_id(user2.id).tags == ["y"] assert User.get_cached_by_id(user2.id).tags == ["y"]
end end
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
assert json_response(conn, :no_content) assert json_response(conn, :no_content)
assert User.get_by_id(user3.id).tags == ["unchanged"] assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
end end
end end
@ -257,7 +257,7 @@ test "deactivates the user", %{conn: conn} do
conn conn
|> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false}) |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.deactivated == true assert user.info.deactivated == true
assert json_response(conn, :no_content) assert json_response(conn, :no_content)
end end
@ -269,7 +269,7 @@ test "activates the user", %{conn: conn} do
conn conn
|> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true}) |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true})
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
assert user.info.deactivated == false assert user.info.deactivated == false
assert json_response(conn, :no_content) assert json_response(conn, :no_content)
end end

View File

@ -119,6 +119,31 @@ test "works for bare text/markdown" do
assert output == expected assert output == expected
end end
test "works for bare text/bbcode" do
text = "[b]hello world[/b]"
expected = "<strong>hello world</strong>"
{output, [], []} = Utils.format_input(text, "text/bbcode")
assert output == expected
text = "[b]hello world![/b]\n\nsecond paragraph!"
expected = "<strong>hello world!</strong><br>\n<br>\nsecond paragraph!"
{output, [], []} = Utils.format_input(text, "text/bbcode")
assert output == expected
text = "[b]hello world![/b]\n\n<strong>second paragraph!</strong>"
expected =
"<strong>hello world!</strong><br>\n<br>\n&lt;strong&gt;second paragraph!&lt;/strong&gt;"
{output, [], []} = Utils.format_input(text, "text/bbcode")
assert output == expected
end
test "works for text/markdown with mentions" do test "works for text/markdown with mentions" do
{:ok, user} = {:ok, user} =
UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"}) UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"})

View File

@ -56,14 +56,17 @@ test "Represent a user account" do
bot: false, bot: false,
source: %{ source: %{
note: "", note: "",
privacy: "public", sensitive: false,
sensitive: false pleroma: %{}
}, },
pleroma: %{ pleroma: %{
confirmation_pending: false, confirmation_pending: false,
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
hide_favorites: true,
hide_followers: false,
hide_follows: false,
relationship: %{} relationship: %{}
} }
} }
@ -81,8 +84,12 @@ test "Represent the user account for the account owner" do
"follows" => true "follows" => true
} }
assert %{pleroma: %{notification_settings: ^notification_settings}} = privacy = user.info.default_scope
AccountView.render("account.json", %{user: user, for: user})
assert %{
pleroma: %{notification_settings: ^notification_settings},
source: %{privacy: ^privacy}
} = AccountView.render("account.json", %{user: user, for: user})
end end
test "Represent a Service(bot) account" do test "Represent a Service(bot) account" do
@ -114,14 +121,17 @@ test "Represent a Service(bot) account" do
bot: true, bot: true,
source: %{ source: %{
note: "", note: "",
privacy: "public", sensitive: false,
sensitive: false pleroma: %{}
}, },
pleroma: %{ pleroma: %{
confirmation_pending: false, confirmation_pending: false,
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
hide_favorites: true,
hide_followers: false,
hide_follows: false,
relationship: %{} relationship: %{}
} }
} }
@ -169,15 +179,15 @@ test "represent a relationship" do
test "represent an embedded relationship" do test "represent an embedded relationship" do
user = user =
insert(:user, %{ insert(:user, %{
info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}}, info: %{note_count: 5, follower_count: 0, source_data: %{"type" => "Service"}},
nickname: "shp@shitposter.club", nickname: "shp@shitposter.club",
inserted_at: ~N[2017-08-15 15:47:06.597036] inserted_at: ~N[2017-08-15 15:47:06.597036]
}) })
other_user = insert(:user) other_user = insert(:user)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user} = User.follow(other_user, user)
{:ok, other_user} = User.block(other_user, user) {:ok, other_user} = User.block(other_user, user)
{:ok, _} = User.follow(insert(:user), user)
expected = %{ expected = %{
id: to_string(user.id), id: to_string(user.id),
@ -186,7 +196,7 @@ test "represent an embedded relationship" do
display_name: user.name, display_name: user.name,
locked: false, locked: false,
created_at: "2017-08-15T15:47:06.000Z", created_at: "2017-08-15T15:47:06.000Z",
followers_count: 3, followers_count: 1,
following_count: 0, following_count: 0,
statuses_count: 5, statuses_count: 5,
note: user.bio, note: user.bio,
@ -200,14 +210,17 @@ test "represent an embedded relationship" do
bot: true, bot: true,
source: %{ source: %{
note: "", note: "",
privacy: "public", sensitive: false,
sensitive: false pleroma: %{}
}, },
pleroma: %{ pleroma: %{
confirmation_pending: false, confirmation_pending: false,
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
hide_favorites: true,
hide_followers: false,
hide_follows: false,
relationship: %{ relationship: %{
id: to_string(user.id), id: to_string(user.id),
following: false, following: false,

View File

@ -445,7 +445,7 @@ test "get a status", %{conn: conn} do
describe "deleting a status" do describe "deleting a status" do
test "when you created it", %{conn: conn} do test "when you created it", %{conn: conn} do
activity = insert(:note_activity) activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"]) author = User.get_cached_by_ap_id(activity.data["actor"])
conn = conn =
conn conn
@ -1022,7 +1022,7 @@ test "reblogged status for another user", %{conn: conn} do
user2 = insert(:user) user2 = insert(:user)
user3 = insert(:user) user3 = insert(:user)
CommonAPI.favorite(activity.id, user2) CommonAPI.favorite(activity.id, user2)
{:ok, user2} = User.bookmark(user2, activity.data["object"]["id"]) {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
{:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
{:ok, _, _object} = CommonAPI.repeat(activity.id, user2) {:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
@ -1167,7 +1167,7 @@ test "gets a users statuses", %{conn: conn} do
test "unimplemented pinned statuses feature", %{conn: conn} do test "unimplemented pinned statuses feature", %{conn: conn} do
note = insert(:note_activity) note = insert(:note_activity)
user = User.get_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
conn = conn =
conn conn
@ -1178,7 +1178,7 @@ test "unimplemented pinned statuses feature", %{conn: conn} do
test "gets an users media", %{conn: conn} do test "gets an users media", %{conn: conn} do
note = insert(:note_activity) note = insert(:note_activity)
user = User.get_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/jpg", content_type: "image/jpg",
@ -1253,8 +1253,8 @@ test "/api/v1/follow_requests works" do
{:ok, _activity} = ActivityPub.follow(other_user, user) {:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
@ -1273,8 +1273,8 @@ test "/api/v1/follow_requests/:id/authorize works" do
{:ok, _activity} = ActivityPub.follow(other_user, user) {:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
@ -1286,8 +1286,8 @@ test "/api/v1/follow_requests/:id/authorize works" do
assert relationship = json_response(conn, 200) assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"] assert to_string(other_user.id) == relationship["id"]
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == true assert User.following?(other_user, user) == true
end end
@ -1310,7 +1310,7 @@ test "/api/v1/follow_requests/:id/reject works" do
{:ok, _activity} = ActivityPub.follow(other_user, user) {:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
conn = conn =
build_conn() build_conn()
@ -1320,8 +1320,8 @@ test "/api/v1/follow_requests/:id/reject works" do
assert relationship = json_response(conn, 200) assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"] assert to_string(other_user.id) == relationship["id"]
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
end end
@ -1606,7 +1606,7 @@ test "following / unfollowing a user", %{conn: conn} do
assert %{"id" => _id, "following" => true} = json_response(conn, 200) assert %{"id" => _id, "following" => true} = json_response(conn, 200)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
conn = conn =
build_conn() build_conn()
@ -1615,7 +1615,7 @@ test "following / unfollowing a user", %{conn: conn} do
assert %{"id" => _id, "following" => false} = json_response(conn, 200) assert %{"id" => _id, "following" => false} = json_response(conn, 200)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
conn = conn =
build_conn() build_conn()
@ -1709,7 +1709,7 @@ test "muting / unmuting a user", %{conn: conn} do
assert %{"id" => _id, "muting" => true} = json_response(conn, 200) assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
conn = conn =
build_conn() build_conn()
@ -1764,7 +1764,7 @@ test "blocking / unblocking a user", %{conn: conn} do
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
conn = conn =
build_conn() build_conn()
@ -1988,6 +1988,199 @@ test "returns the favorites of a user", %{conn: conn} do
assert [] = json_response(third_conn, 200) assert [] = json_response(third_conn, 200)
end end
describe "getting favorites timeline of specified user" do
setup do
[current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
[current_user: current_user, user: user]
end
test "returns list of statuses favorited by specified user", %{
conn: conn,
current_user: current_user,
user: user
} do
[activity | _] = insert_pair(:note_activity)
CommonAPI.favorite(activity.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
[like] = response
assert length(response) == 1
assert like["id"] == activity.id
end
test "returns favorites for specified user_id when user is not logged in", %{
conn: conn,
user: user
} do
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
response =
conn
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 1
end
test "returns favorited DM only when user is logged in and he is one of recipients", %{
conn: conn,
current_user: current_user,
user: user
} do
{:ok, direct} =
CommonAPI.post(current_user, %{
"status" => "Hi @#{user.nickname}!",
"visibility" => "direct"
})
CommonAPI.favorite(direct.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 1
anonymous_response =
conn
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(anonymous_response) == 0
end
test "does not return others' favorited DM when user is not one of recipients", %{
conn: conn,
current_user: current_user,
user: user
} do
user_two = insert(:user)
{:ok, direct} =
CommonAPI.post(user_two, %{
"status" => "Hi @#{user.nickname}!",
"visibility" => "direct"
})
CommonAPI.favorite(direct.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 0
end
test "paginates favorites using since_id and max_id", %{
conn: conn,
current_user: current_user,
user: user
} do
activities = insert_list(10, :note_activity)
Enum.each(activities, fn activity ->
CommonAPI.favorite(activity.id, user)
end)
third_activity = Enum.at(activities, 2)
seventh_activity = Enum.at(activities, 6)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
since_id: third_activity.id,
max_id: seventh_activity.id
})
|> json_response(:ok)
assert length(response) == 3
refute third_activity in response
refute seventh_activity in response
end
test "limits favorites using limit parameter", %{
conn: conn,
current_user: current_user,
user: user
} do
7
|> insert_list(:note_activity)
|> Enum.each(fn activity ->
CommonAPI.favorite(activity.id, user)
end)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
|> json_response(:ok)
assert length(response) == 3
end
test "returns empty response when user does not have any favorited statuses", %{
conn: conn,
current_user: current_user,
user: user
} do
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert Enum.empty?(response)
end
test "returns 404 error when specified user is not exist", %{conn: conn} do
conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
assert json_response(conn, 404) == %{"error" => "Record not found"}
end
test "returns 403 error when user has hidden own favorites", %{
conn: conn,
current_user: current_user
} do
user = insert(:user, %{info: %{hide_favorites: true}})
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
end
test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
user = insert(:user)
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
assert user.info.hide_favorites
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
end
end
describe "updating credentials" do describe "updating credentials" do
test "updates the user's bio", %{conn: conn} do test "updates the user's bio", %{conn: conn} do
user = insert(:user) user = insert(:user)
@ -2021,6 +2214,78 @@ test "updates the user's locking status", %{conn: conn} do
assert user["locked"] == true assert user["locked"] == true
end end
test "updates the user's default scope", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
assert user = json_response(conn, 200)
assert user["source"]["privacy"] == "cofe"
end
test "updates the user's hide_followers status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
assert user = json_response(conn, 200)
assert user["pleroma"]["hide_followers"] == true
end
test "updates the user's hide_follows status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
assert user = json_response(conn, 200)
assert user["pleroma"]["hide_follows"] == true
end
test "updates the user's hide_favorites status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
assert user = json_response(conn, 200)
assert user["pleroma"]["hide_favorites"] == true
end
test "updates the user's show_role status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
assert user = json_response(conn, 200)
assert user["source"]["pleroma"]["show_role"] == false
end
test "updates the user's no_rich_text status", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
assert user = json_response(conn, 200)
assert user["source"]["pleroma"]["no_rich_text"] == true
end
test "updates the user's name", %{conn: conn} do test "updates the user's name", %{conn: conn} do
user = insert(:user) user = insert(:user)
@ -2124,7 +2389,7 @@ test "get instance stats", %{conn: conn} do
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"}) {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
# Stats should count users with missing or nil `info.deactivated` value # Stats should count users with missing or nil `info.deactivated` value
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
info_change = Changeset.change(user.info, %{deactivated: nil}) info_change = Changeset.change(user.info, %{deactivated: nil})
{:ok, _user} = {:ok, _user} =

View File

@ -21,7 +21,7 @@ test "Mention notification" do
mentioned_user = insert(:user) mentioned_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity) {:ok, [notification]} = Notification.create_notifications(activity)
user = User.get_by_id(user.id) user = User.get_cached_by_id(user.id)
expected = %{ expected = %{
id: to_string(notification.id), id: to_string(notification.id),

View File

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -128,6 +129,7 @@ test "a note activity" do
pleroma: %{ pleroma: %{
local: true, local: true,
conversation_id: convo_id, conversation_id: convo_id,
in_reply_to_account_acct: nil,
content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])}, content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])},
spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])} spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])}
} }
@ -152,6 +154,25 @@ test "tells if the message is muted for some reason" do
assert status.muted == true assert status.muted == true
end end
test "tells if the status is bookmarked" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"})
status = StatusView.render("status.json", %{activity: activity})
assert status.bookmarked == false
status = StatusView.render("status.json", %{activity: activity, for: user})
assert status.bookmarked == false
{:ok, _bookmark} = Bookmark.create(user.id, activity.id)
status = StatusView.render("status.json", %{activity: activity, for: user})
assert status.bookmarked == true
end
test "a reply" do test "a reply" do
note = insert(:note_activity) note = insert(:note_activity)
user = insert(:user) user = insert(:user)
@ -178,7 +199,7 @@ test "contains mentions" do
status = StatusView.render("status.json", %{activity: activity}) status = StatusView.render("status.json", %{activity: activity})
actor = User.get_by_ap_id(activity.actor) actor = User.get_cached_by_ap_id(activity.actor)
assert status.mentions == assert status.mentions ==
Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end) Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end)

View File

@ -6,7 +6,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
@ -41,7 +40,8 @@ test "decodes a salmon with a changed magic key", %{conn: conn} do
assert response(conn, 200) assert response(conn, 200)
# Set a wrong magic-key for a user so it has to refetch # Set a wrong magic-key for a user so it has to refetch
salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1") salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1")
# Wrong key # Wrong key
info_cng = info_cng =
User.Info.remote_user_creation(salmon_user.info, %{ User.Info.remote_user_creation(salmon_user.info, %{
@ -52,7 +52,7 @@ test "decodes a salmon with a changed magic key", %{conn: conn} do
salmon_user salmon_user
|> Ecto.Changeset.change() |> Ecto.Changeset.change()
|> Ecto.Changeset.put_embed(:info, info_cng) |> Ecto.Changeset.put_embed(:info, info_cng)
|> Repo.update() |> User.update_and_set_cache()
conn = conn =
build_conn() build_conn()
@ -86,7 +86,7 @@ test "returns 404 for a missing feed", %{conn: conn} do
test "gets an object", %{conn: conn} do test "gets an object", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
user = User.get_by_ap_id(note_activity.data["actor"]) user = User.get_cached_by_ap_id(note_activity.data["actor"])
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"])) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
url = "/objects/#{uuid}" url = "/objects/#{uuid}"

Some files were not shown because too many files have changed in this diff Show More