Merge branch 'develop' into gun
This commit is contained in:
commit
814b275af7
|
@ -76,6 +76,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
|
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
|
||||||
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
|
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
|
||||||
- Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches.
|
- Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches.
|
||||||
|
- ActivityPub: support for `replies` collection (output for outgoing federation & fetching on incoming federation).
|
||||||
|
- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
|
@ -103,6 +105,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: `feed` option for user atom feed.
|
- Configuration: `feed` option for user atom feed.
|
||||||
- Pleroma API: Add Emoji reactions
|
- Pleroma API: Add Emoji reactions
|
||||||
- Admin API: Add `/api/pleroma/admin/instances/:instance/statuses` - lists all statuses from a given instance
|
- Admin API: Add `/api/pleroma/admin/instances/:instance/statuses` - lists all statuses from a given instance
|
||||||
|
- Admin API: Add `/api/pleroma/admin/users/:nickname/statuses` - lists all statuses from a given user
|
||||||
- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
|
- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
|
||||||
- ActivityPub: Configurable `type` field of the actors.
|
- ActivityPub: Configurable `type` field of the actors.
|
||||||
- Mastodon API: `/api/v1/accounts/:id` has `source/pleroma/actor_type` field.
|
- Mastodon API: `/api/v1/accounts/:id` has `source/pleroma/actor_type` field.
|
||||||
|
@ -118,6 +121,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
|
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
|
||||||
- Mastodon API: Add `reacted` property to `emoji_reactions`
|
- Mastodon API: Add `reacted` property to `emoji_reactions`
|
||||||
- Pleroma API: Add reactions for a single emoji.
|
- Pleroma API: Add reactions for a single emoji.
|
||||||
|
- ActivityPub: `[:activitypub, :note_replies_output_limit]` setting sets the number of note self-replies to output on outgoing federation.
|
||||||
|
- Admin API: `GET /api/pleroma/admin/stats` to get status count by visibility scope
|
||||||
|
- Admin API: `GET /api/pleroma/admin/statuses` - list all statuses (accepts `godmode` and `local_only`)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -198,6 +198,8 @@
|
||||||
max_expiration: 365 * 24 * 60 * 60
|
max_expiration: 365 * 24 * 60 * 60
|
||||||
},
|
},
|
||||||
registrations_open: true,
|
registrations_open: true,
|
||||||
|
invites_enabled: false,
|
||||||
|
account_activation_required: false,
|
||||||
federating: true,
|
federating: true,
|
||||||
federation_incoming_replies_max_depth: 100,
|
federation_incoming_replies_max_depth: 100,
|
||||||
federation_reachability_timeout_days: 7,
|
federation_reachability_timeout_days: 7,
|
||||||
|
@ -305,6 +307,7 @@
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
outgoing_blocks: true,
|
outgoing_blocks: true,
|
||||||
follow_handshake_timeout: 500,
|
follow_handshake_timeout: 500,
|
||||||
|
note_replies_output_limit: 5,
|
||||||
sign_object_fetches: true,
|
sign_object_fetches: true,
|
||||||
authorized_fetch_mode: false
|
authorized_fetch_mode: false
|
||||||
|
|
||||||
|
@ -378,6 +381,8 @@
|
||||||
|
|
||||||
config :phoenix, :json_library, Jason
|
config :phoenix, :json_library, Jason
|
||||||
|
|
||||||
|
config :phoenix, :filter_parameters, ["password", "confirm"]
|
||||||
|
|
||||||
config :pleroma, :gopher,
|
config :pleroma, :gopher,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
ip: {0, 0, 0, 0},
|
ip: {0, 0, 0, 0},
|
||||||
|
@ -460,6 +465,7 @@
|
||||||
transmogrifier: 20,
|
transmogrifier: 20,
|
||||||
scheduled_activities: 10,
|
scheduled_activities: 10,
|
||||||
background: 5,
|
background: 5,
|
||||||
|
remote_fetcher: 2,
|
||||||
attachments_cleanup: 5,
|
attachments_cleanup: 5,
|
||||||
new_users_digest: 1
|
new_users_digest: 1
|
||||||
],
|
],
|
||||||
|
|
|
@ -662,7 +662,7 @@
|
||||||
label: "Fed. incoming replies max depth",
|
label: "Fed. incoming replies max depth",
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description:
|
description:
|
||||||
"Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while" <>
|
"Max. depth of reply-to and reply activities fetching on incoming federation, to prevent out-of-memory situations while" <>
|
||||||
" fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.",
|
" fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
100
|
100
|
||||||
|
@ -1615,160 +1615,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: Pleroma.Web.Endpoint,
|
|
||||||
type: :group,
|
|
||||||
description: "Phoenix endpoint configuration",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :http,
|
|
||||||
label: "HTTP",
|
|
||||||
type: {:keyword, :integer, :tuple},
|
|
||||||
description: "http protocol configuration",
|
|
||||||
suggestions: [
|
|
||||||
port: 8080,
|
|
||||||
ip: {127, 0, 0, 1}
|
|
||||||
],
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :dispatch,
|
|
||||||
type: {:list, :tuple},
|
|
||||||
description: "dispatch settings",
|
|
||||||
suggestions: [
|
|
||||||
{:_,
|
|
||||||
[
|
|
||||||
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
|
|
||||||
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
|
|
||||||
{Phoenix.Transports.WebSocket,
|
|
||||||
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
|
|
||||||
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
|
|
||||||
]}
|
|
||||||
# end copied from config.exs
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :ip,
|
|
||||||
label: "IP",
|
|
||||||
type: :tuple,
|
|
||||||
description: "ip",
|
|
||||||
suggestions: [
|
|
||||||
{0, 0, 0, 0}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :port,
|
|
||||||
type: :integer,
|
|
||||||
description: "port",
|
|
||||||
suggestions: [
|
|
||||||
2020
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :url,
|
|
||||||
label: "URL",
|
|
||||||
type: {:keyword, :string, :integer},
|
|
||||||
description: "configuration for generating urls",
|
|
||||||
suggestions: [
|
|
||||||
host: "example.com",
|
|
||||||
port: 2020,
|
|
||||||
scheme: "https"
|
|
||||||
],
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :host,
|
|
||||||
type: :string,
|
|
||||||
description: "Host",
|
|
||||||
suggestions: [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :port,
|
|
||||||
type: :integer,
|
|
||||||
description: "port",
|
|
||||||
suggestions: [
|
|
||||||
2020
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :scheme,
|
|
||||||
type: :string,
|
|
||||||
description: "Scheme",
|
|
||||||
suggestions: [
|
|
||||||
"https",
|
|
||||||
"https"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :instrumenters,
|
|
||||||
type: {:list, :module},
|
|
||||||
suggestions: [Pleroma.Web.Endpoint.Instrumenter]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :protocol,
|
|
||||||
type: :string,
|
|
||||||
suggestions: ["https"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :secret_key_base,
|
|
||||||
type: :string,
|
|
||||||
suggestions: ["aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :signing_salt,
|
|
||||||
type: :string,
|
|
||||||
suggestions: ["CqaoopA2"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :render_errors,
|
|
||||||
type: :keyword,
|
|
||||||
suggestions: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :view,
|
|
||||||
type: :module,
|
|
||||||
suggestions: [Pleroma.Web.ErrorView]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :accepts,
|
|
||||||
type: {:list, :string},
|
|
||||||
suggestions: ["json"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :pubsub,
|
|
||||||
type: :keyword,
|
|
||||||
suggestions: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2],
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :name,
|
|
||||||
type: :module,
|
|
||||||
suggestions: [Pleroma.PubSub]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :adapter,
|
|
||||||
type: :module,
|
|
||||||
suggestions: [Phoenix.PubSub.PG2]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :secure_cookie_flag,
|
|
||||||
type: :boolean
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :extra_cookie_attrs,
|
|
||||||
type: {:list, :string},
|
|
||||||
suggestions: ["SameSite=Lax"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :activitypub,
|
key: :activitypub,
|
||||||
|
@ -1790,6 +1636,12 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Sign object fetches with HTTP signatures"
|
description: "Sign object fetches with HTTP signatures"
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :note_replies_output_limit,
|
||||||
|
type: :integer,
|
||||||
|
description:
|
||||||
|
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)."
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :follow_handshake_timeout,
|
key: :follow_handshake_timeout,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
@ -2588,19 +2440,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :database,
|
|
||||||
type: :group,
|
|
||||||
description: "Database related settings",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :rum_enabled,
|
|
||||||
type: :boolean,
|
|
||||||
description: "If RUM indexes should be used. Default: disabled"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :rate_limit,
|
key: :rate_limit,
|
||||||
|
@ -2764,20 +2603,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :prometheus,
|
|
||||||
key: Pleroma.Web.Endpoint.MetricsExporter,
|
|
||||||
type: :group,
|
|
||||||
description: "Prometheus settings",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :path,
|
|
||||||
type: :string,
|
|
||||||
description: "API endpoint with metrics",
|
|
||||||
suggestions: ["/api/pleroma/app_metrics"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
group: :http_signatures,
|
group: :http_signatures,
|
||||||
type: :group,
|
type: :group,
|
||||||
|
@ -3045,7 +2870,7 @@
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :feed,
|
key: :feed,
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Configure feed rendering.",
|
description: "Configure feed rendering",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :post_title,
|
key: :post_title,
|
||||||
|
@ -3095,7 +2920,7 @@
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :modules,
|
key: :modules,
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Custom Runtime Modules.",
|
description: "Custom Runtime Modules",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :runtime_dir,
|
key: :runtime_dir,
|
||||||
|
@ -3103,18 +2928,5 @@
|
||||||
description: "A path to custom Elixir modules (such as MRF policies)."
|
description: "A path to custom Elixir modules (such as MRF policies)."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
type: :group,
|
|
||||||
description: "Allow instance configuration from database.",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :configurable_from_database,
|
|
||||||
type: :boolean,
|
|
||||||
description:
|
|
||||||
"Allow transferring configuration to DB with the subsequent customization from Admin api. Default: disabled"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -260,10 +260,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- `nickname` or `id`
|
- `nickname` or `id`
|
||||||
- *optional* `page_size`: number of statuses to return (default is `20`)
|
- *optional* `page_size`: number of statuses to return (default is `20`)
|
||||||
- *optional* `godmode`: `true`/`false` – allows to see private statuses
|
- *optional* `godmode`: `true`/`false` – allows to see private statuses
|
||||||
|
- *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false)
|
||||||
- Response:
|
- Response:
|
||||||
- On failure: `Not found`
|
- On failure: `Not found`
|
||||||
- On success: JSON array of user's latest statuses
|
- On success: JSON array of user's latest statuses
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/instances/:instance/statuses`
|
||||||
|
|
||||||
|
### Retrive instance's latest statuses
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
- `instance`: instance name
|
||||||
|
- *optional* `page_size`: number of statuses to return (default is `20`)
|
||||||
|
- *optional* `godmode`: `true`/`false` – allows to see private statuses
|
||||||
|
- *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false)
|
||||||
|
- Response:
|
||||||
|
- On failure: `Not found`
|
||||||
|
- On success: JSON array of instance's latest statuses
|
||||||
|
|
||||||
## `POST /api/pleroma/admin/relay`
|
## `POST /api/pleroma/admin/relay`
|
||||||
|
|
||||||
### Follow a Relay
|
### Follow a Relay
|
||||||
|
@ -941,3 +955,20 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
- Params:
|
- Params:
|
||||||
- `nicknames`
|
- `nicknames`
|
||||||
- Response: Array of user nicknames
|
- Response: Array of user nicknames
|
||||||
|
|
||||||
|
## `GET /api/pleroma/admin/stats`
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status_visibility": {
|
||||||
|
"direct": 739,
|
||||||
|
"private": 9,
|
||||||
|
"public": 17,
|
||||||
|
"unlisted": 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
6. Run `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>`
|
6. Run `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>`
|
||||||
7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
|
7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
|
||||||
8. Restart the Pleroma service.
|
8. Restart the Pleroma service.
|
||||||
|
9. After you've restarted Pleroma, you will notice that postgres will take up more cpu resources than usual. A lot in fact. To fix this you must do a VACUUM ANLAYZE. This can also be done while the instance is still running like so:
|
||||||
|
$ sudo -u postgres psql pleroma_database_name
|
||||||
|
pleroma=# VACUUM ANALYZE;
|
||||||
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
||||||
|
|
||||||
## Remove
|
## Remove
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Theming your instance
|
||||||
|
|
||||||
|
To add a custom theme to your instance, you'll first need to get a custom theme, upload it to the server, make it available to the instance and eventually you can set it as default.
|
||||||
|
|
||||||
|
## Getting a custom theme
|
||||||
|
|
||||||
|
### Create your own theme
|
||||||
|
|
||||||
|
* You can create your own theme using the Pleroma FE by going to settings (gear on the top right) and choose the Theme tab. Here you have the options to create a personal theme.
|
||||||
|
* To download your theme, you can do Save preset
|
||||||
|
* If you want to upload a theme to customise it further, you can upload it using Load preset
|
||||||
|
|
||||||
|
This will only save the theme for you personally. To make it available to the whole instance, you'll need to upload it to the server.
|
||||||
|
|
||||||
|
### Get an existing theme
|
||||||
|
|
||||||
|
* You can download a theme from another instance by going to that instance, go to settings and make sure you have the theme selected that you want. Then you can do Save preset to download it.
|
||||||
|
* You can also find and download custom themes at <https://plthemes.vulpes.one/>
|
||||||
|
|
||||||
|
## Adding the custom theme to the instance
|
||||||
|
|
||||||
|
### Upload the theme to the server
|
||||||
|
|
||||||
|
Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `priv/static/static/styles.json`.
|
||||||
|
|
||||||
|
Example of `styles.json` where we add our own `my-awesome-theme.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
|
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
|
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||||
|
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
||||||
|
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
||||||
|
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
|
||||||
|
|
||||||
|
"redmond-xx": "/static/themes/redmond-xx.json",
|
||||||
|
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
|
||||||
|
"redmond-xxi": "/static/themes/redmond-xxi.json",
|
||||||
|
"breezy-dark": "/static/themes/breezy-dark.json",
|
||||||
|
"breezy-light": "/static/themes/breezy-light.json",
|
||||||
|
"mammal": "/static/themes/mammal.json",
|
||||||
|
"my-awesome-theme": "/static/themes/my-awesome-theme.json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you'll already be able to select the theme in Pleroma FE from the drop-down. You don't need to restart Pleroma because we only changed static served files. You may need to refresh the page in your browser. You'll notice however that the theme doesn't have a name, it's just an empty entry in the drop-down.
|
||||||
|
|
||||||
|
### Give the theme a name
|
||||||
|
|
||||||
|
When you open one of the themes that ship with Pleroma, you'll notice that the json has a `"name"` key. Add a key-value pair to your theme where the key name is `"name"` and the value the name you want to give your theme. After this you can refresh te page in your browser and the name should be visible in the drop-down.
|
||||||
|
|
||||||
|
Example of `my-awesome-theme.json` where we add the name "My Awesome Theme"
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_pleroma_theme_version": 2,
|
||||||
|
"name": "My Awesome Theme",
|
||||||
|
"theme": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set as default theme
|
||||||
|
|
||||||
|
Now we can set the new theme as default in the [Pleroma FE configuration](General-tips-for-customizing-Pleroma-FE.md).
|
||||||
|
|
||||||
|
Example of adding the new theme in the back-end config files
|
||||||
|
```elixir
|
||||||
|
config :pleroma, :frontend_configurations,
|
||||||
|
pleroma_fe: %{
|
||||||
|
theme: "my-awesome-theme"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you added it in the back-end configuration file, you'll need to restart your instance for the changes to take effect. If you don't see the changes, it's probably because the browser has cached the previous theme. In that case you'll want to clear browser caches. Alternatively you can use a private/incognito window just to see the changes.
|
||||||
|
|
|
@ -41,7 +41,7 @@ On the top right you will also see a wrench icon. This opens your personal setti
|
||||||
This is where the interesting stuff happens!
|
This is where the interesting stuff happens!
|
||||||
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
||||||
|
|
||||||
- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). Clicking on the profile pic will uncollapse the user's profile.
|
- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the reply-to status). Clicking on the profile pic will uncollapse the user's profile.
|
||||||
- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
||||||
- An arrow icon allows you to open the status on the instance where it's originating from.
|
- An arrow icon allows you to open the status on the instance where it's originating from.
|
||||||
- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.
|
- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.
|
||||||
|
|
|
@ -185,11 +185,7 @@ def run(["gen-pack", src]) do
|
||||||
|
|
||||||
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
|
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
|
||||||
:zip.unzip(
|
|
||||||
binary_archive,
|
|
||||||
cwd: tmp_pack_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
@shortdoc "Manages Pleroma instance"
|
@shortdoc "Manages Pleroma instance"
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
|
||||||
|
|
||||||
|
@ -63,7 +65,8 @@ def run(["gen" | rest]) do
|
||||||
get_option(
|
get_option(
|
||||||
options,
|
options,
|
||||||
:instance_name,
|
:instance_name,
|
||||||
"What is the name of your instance? (e.g. Pleroma/Soykaf)"
|
"What is the name of your instance? (e.g. The Corndog Emporium)",
|
||||||
|
domain
|
||||||
)
|
)
|
||||||
|
|
||||||
email = get_option(options, :admin_email, "What is your admin email address?")
|
email = get_option(options, :admin_email, "What is your admin email address?")
|
||||||
|
@ -153,6 +156,8 @@ def run(["gen" | rest]) do
|
||||||
Pleroma.Config.get([:instance, :static_dir])
|
Pleroma.Config.get([:instance, :static_dir])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Config.put([:instance, :static_dir], static_dir)
|
||||||
|
|
||||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
||||||
|
@ -202,8 +207,14 @@ def run(["gen" | rest]) do
|
||||||
write_robots_txt(indexable, template_dir)
|
write_robots_txt(indexable, template_dir)
|
||||||
|
|
||||||
shell_info(
|
shell_info(
|
||||||
"\n All files successfully written! Refer to the installation instructions for your platform for next steps"
|
"\n All files successfully written! Refer to the installation instructions for your platform for next steps."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if db_configurable? do
|
||||||
|
shell_info(
|
||||||
|
" Please transfer your config to the database after running database migrations. Refer to \"Transfering the config to/from the database\" section of the docs for more information."
|
||||||
|
)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
shell_error(
|
shell_error(
|
||||||
"The task would have overwritten the following files:\n" <>
|
"The task would have overwritten the following files:\n" <>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
|
||||||
|
@shortdoc "Refreshes counter cache"
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.CounterCache
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def run([]) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
|
|
||||||
|
["public", "unlisted", "private", "direct"]
|
||||||
|
|> Enum.each(fn visibility ->
|
||||||
|
count = status_visibility_count_query(visibility)
|
||||||
|
name = "status_visibility_#{visibility}"
|
||||||
|
CounterCache.set(name, count)
|
||||||
|
Mix.Pleroma.shell_info("Set #{name} to #{count}")
|
||||||
|
end)
|
||||||
|
|
||||||
|
Mix.Pleroma.shell_info("Done")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp status_visibility_count_query(visibility) do
|
||||||
|
Activity
|
||||||
|
|> where(
|
||||||
|
[a],
|
||||||
|
fragment(
|
||||||
|
"activity_visibility(?, ?, ?) = ?",
|
||||||
|
a.actor,
|
||||||
|
a.recipients,
|
||||||
|
a.data,
|
||||||
|
^visibility
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
||||||
|
|> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
|
||||||
|
end
|
||||||
|
end
|
|
@ -100,8 +100,7 @@ def run(["rm", nickname]) do
|
||||||
User.perform(:delete, user)
|
User.perform(:delete, user)
|
||||||
shell_info("User #{nickname} deleted.")
|
shell_info("User #{nickname} deleted.")
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> shell_error("No local user #{nickname}")
|
||||||
shell_error("No local user #{nickname}")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,7 @@ def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
|
||||||
|
|
||||||
def restrict_deactivated_users(query) do
|
def restrict_deactivated_users(query) do
|
||||||
deactivated_users =
|
deactivated_users =
|
||||||
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
Activity.Queries.exclude_authors(query, deactivated_users)
|
Activity.Queries.exclude_authors(query, deactivated_users)
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do
|
||||||
Contains queries for Activity.
|
Contains queries for Activity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Ecto.Query, only: [from: 2]
|
import Ecto.Query, only: [from: 2, where: 3]
|
||||||
|
|
||||||
@type query :: Ecto.Queryable.t() | Activity.t()
|
@type query :: Ecto.Queryable.t() | Activity.t()
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ def by_actor(query \\ Activity, actor) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec by_author(query, String.t()) :: query
|
@spec by_author(query, User.t()) :: query
|
||||||
def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
||||||
from(a in query, where: a.actor == ^ap_id)
|
from(a in query, where: a.actor == ^ap_id)
|
||||||
end
|
end
|
||||||
|
@ -63,6 +63,22 @@ def by_object_id(query, object_id) when is_binary(object_id) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query
|
||||||
|
def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do
|
||||||
|
query =
|
||||||
|
if opts[:skip_preloading] do
|
||||||
|
Activity.with_joined_object(query)
|
||||||
|
else
|
||||||
|
Activity.with_preloaded_object(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
where(
|
||||||
|
query,
|
||||||
|
[activity, object: o],
|
||||||
|
fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@spec by_type(query, String.t()) :: query
|
@spec by_type(query, String.t()) :: query
|
||||||
def by_type(query \\ Activity, activity_type) do
|
def by_type(query \\ Activity, activity_type) do
|
||||||
from(
|
from(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Pleroma: A lightweight social networking server
|
# Pleroma: A lightweight social networking server
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Captcha do
|
defmodule Pleroma.Captcha do
|
||||||
|
@ -50,7 +50,7 @@ def handle_call(:new, _from, state) do
|
||||||
token = new_captcha[:token]
|
token = new_captcha[:token]
|
||||||
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
|
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
|
||||||
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
|
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
|
||||||
# Basicallty copy what Phoenix.Token does here, add the time to
|
# Basically copy what Phoenix.Token does here, add the time to
|
||||||
# the actual data and make it a binary to then encrypt it
|
# the actual data and make it a binary to then encrypt it
|
||||||
encrypted_captcha_answer =
|
encrypted_captcha_answer =
|
||||||
%{
|
%{
|
||||||
|
@ -62,7 +62,7 @@ def handle_call(:new, _from, state) do
|
||||||
|
|
||||||
{
|
{
|
||||||
:reply,
|
:reply,
|
||||||
# Repalce the answer with the encrypted answer
|
# Replace the answer with the encrypted answer
|
||||||
%{new_captcha | answer_data: encrypted_captcha_answer},
|
%{new_captcha | answer_data: encrypted_captcha_answer},
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,8 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
|
||||||
valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid)
|
valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
with false <- is_nil(answer_data),
|
||||||
|
{:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
||||||
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
||||||
try do
|
try do
|
||||||
if DateTime.before?(at, valid_if_after),
|
if DateTime.before?(at, valid_if_after),
|
||||||
|
|
|
@ -10,8 +10,8 @@ defmodule Pleroma.Captcha.Native do
|
||||||
@impl Service
|
@impl Service
|
||||||
def new do
|
def new do
|
||||||
case Captcha.get() do
|
case Captcha.get() do
|
||||||
{:timeout} ->
|
:error ->
|
||||||
%{error: dgettext("errors", "Captcha timeout")}
|
%{error: dgettext("errors", "Captcha error")}
|
||||||
|
|
||||||
{:ok, answer_data, img_binary} ->
|
{:ok, answer_data, img_binary} ->
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -133,10 +133,8 @@ def restrict_recipients(query, user, %{"recipients" => user_ids}) do
|
||||||
[user.id | user_ids]
|
[user.id | user_ids]
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> Enum.reduce([], fn user_id, acc ->
|
|> Enum.reduce([], fn user_id, acc ->
|
||||||
case FlakeId.Ecto.CompatType.dump(user_id) do
|
{:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
|
||||||
{:ok, user_id} -> [user_id | acc]
|
[user_id | acc]
|
||||||
_ -> acc
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conversation_subquery =
|
conversation_subquery =
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.CounterCache do
|
||||||
|
alias Pleroma.CounterCache
|
||||||
|
alias Pleroma.Repo
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
schema "counter_cache" do
|
||||||
|
field(:name, :string)
|
||||||
|
field(:count, :integer)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, params) do
|
||||||
|
struct
|
||||||
|
|> cast(params, [:name, :count])
|
||||||
|
|> validate_required([:name])
|
||||||
|
|> unique_constraint(:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_as_map(names) when is_list(names) do
|
||||||
|
CounterCache
|
||||||
|
|> where([cc], cc.name in ^names)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.group_by(& &1.name, & &1.count)
|
||||||
|
|> Map.new(fn {k, v} -> {k, hd(v)} end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(name, count) do
|
||||||
|
%CounterCache{}
|
||||||
|
|> changeset(%{"name" => name, "count" => count})
|
||||||
|
|> Repo.insert(
|
||||||
|
on_conflict: [set: [count: count]],
|
||||||
|
returning: true,
|
||||||
|
conflict_target: :name
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.MIME do
|
||||||
@default "application/octet-stream"
|
@default "application/octet-stream"
|
||||||
@read_bytes 35
|
@read_bytes 35
|
||||||
|
|
||||||
@spec file_mime_type(String.t()) ::
|
@spec file_mime_type(String.t(), String.t()) ::
|
||||||
{:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
|
{:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
|
||||||
def file_mime_type(path, filename) do
|
def file_mime_type(path, filename) do
|
||||||
with {:ok, content_type} <- file_mime_type(path),
|
with {:ok, content_type} <- file_mime_type(path),
|
||||||
|
|
|
@ -301,4 +301,26 @@ def update_data(%Object{data: data} = object, attrs \\ %{}) do
|
||||||
def local?(%Object{data: %{"id" => id}}) do
|
def local?(%Object{data: %{"id" => id}}) do
|
||||||
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
|
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replies(object, opts \\ []) do
|
||||||
|
object = Object.normalize(object)
|
||||||
|
|
||||||
|
query =
|
||||||
|
Object
|
||||||
|
|> where(
|
||||||
|
[o],
|
||||||
|
fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"])
|
||||||
|
)
|
||||||
|
|> order_by([o], asc: o.id)
|
||||||
|
|
||||||
|
if opts[:self_only] do
|
||||||
|
actor = object.data["actor"]
|
||||||
|
where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor))
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self_replies(object, opts \\ []),
|
||||||
|
do: replies(object, Keyword.put(opts, :self_only, true))
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,15 +39,8 @@ def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor)
|
||||||
defp compare_uris(_, %URI{scheme: "tag"}), do: :ok
|
defp compare_uris(_, %URI{scheme: "tag"}), do: :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compare_uris(%URI{} = id_uri, %URI{} = other_uri) do
|
defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
|
||||||
if id_uri.host == other_uri.host do
|
defp compare_uris(_id_uri, _other_uri), do: :error
|
||||||
:ok
|
|
||||||
else
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp compare_uris(_, _), do: :error
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Checks that an imported AP object's actor matches the domain it came from.
|
Checks that an imported AP object's actor matches the domain it came from.
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
alias Pleroma.Signature
|
alias Pleroma.Signature
|
||||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.Federator
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
@ -59,20 +60,23 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO:
|
# Note: will create a Create activity, which we need internally at the moment.
|
||||||
# This will create a Create activity, which we need internally at the moment.
|
|
||||||
def fetch_object_from_id(id, options \\ []) do
|
def fetch_object_from_id(id, options \\ []) do
|
||||||
with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||||
{:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
||||||
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
|
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||||
|
{_, nil} <- {:normalize, Object.normalize(data, false)},
|
||||||
params <- prepare_activity_params(data),
|
params <- prepare_activity_params(data),
|
||||||
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
{_, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
||||||
{:transmogrifier, {:ok, activity}} <-
|
{_, {:ok, activity}} <-
|
||||||
{:transmogrifier, Transmogrifier.handle_incoming(params, options)},
|
{:transmogrifier, Transmogrifier.handle_incoming(params, options)},
|
||||||
{:object, _data, %Object{} = object} <-
|
{_, _data, %Object{} = object} <-
|
||||||
{:object, data, Object.normalize(activity, false)} do
|
{:object, data, Object.normalize(activity, false)} do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
else
|
else
|
||||||
|
{:allowed_depth, false} ->
|
||||||
|
{:error, "Max thread distance exceeded."}
|
||||||
|
|
||||||
{:containment, _} ->
|
{:containment, _} ->
|
||||||
{:error, "Object containment failed."}
|
{:error, "Object containment failed."}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Stats do
|
defmodule Pleroma.Stats do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
alias Pleroma.CounterCache
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@ -96,4 +97,21 @@ defp get_stat_data do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_status_visibility_count do
|
||||||
|
counter_cache =
|
||||||
|
CounterCache.get_as_map([
|
||||||
|
"status_visibility_public",
|
||||||
|
"status_visibility_private",
|
||||||
|
"status_visibility_unlisted",
|
||||||
|
"status_visibility_direct"
|
||||||
|
])
|
||||||
|
|
||||||
|
%{
|
||||||
|
public: counter_cache["status_visibility_public"] || 0,
|
||||||
|
unlisted: counter_cache["status_visibility_unlisted"] || 0,
|
||||||
|
private: counter_cache["status_visibility_private"] || 0,
|
||||||
|
direct: counter_cache["status_visibility_direct"] || 0
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -853,14 +853,14 @@ def get_followers_query(user, page) do
|
||||||
@spec get_followers_query(User.t()) :: Ecto.Query.t()
|
@spec get_followers_query(User.t()) :: Ecto.Query.t()
|
||||||
def get_followers_query(user), do: get_followers_query(user, nil)
|
def get_followers_query(user), do: get_followers_query(user, nil)
|
||||||
|
|
||||||
@spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
|
@spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
|
||||||
def get_followers(user, page \\ nil) do
|
def get_followers(user, page \\ nil) do
|
||||||
user
|
user
|
||||||
|> get_followers_query(page)
|
|> get_followers_query(page)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
|
@spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
|
||||||
def get_external_followers(user, page \\ nil) do
|
def get_external_followers(user, page \\ nil) do
|
||||||
user
|
user
|
||||||
|> get_followers_query(page)
|
|> get_followers_query(page)
|
||||||
|
@ -1304,7 +1304,6 @@ def perform(:delete, %User{} = user) do
|
||||||
Repo.delete(user)
|
Repo.delete(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
|
||||||
def perform(:fetch_initial_posts, %User{} = user) do
|
def perform(:fetch_initial_posts, %User{} = user) do
|
||||||
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
||||||
|
|
||||||
|
@ -1336,7 +1335,6 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
|
|
||||||
def perform(:follow_import, %User{} = follower, followed_identifiers)
|
def perform(:follow_import, %User{} = follower, followed_identifiers)
|
||||||
when is_list(followed_identifiers) do
|
when is_list(followed_identifiers) do
|
||||||
Enum.map(
|
Enum.map(
|
||||||
|
|
|
@ -48,7 +48,7 @@ defmodule Pleroma.User.Query do
|
||||||
followers: User.t(),
|
followers: User.t(),
|
||||||
friends: User.t(),
|
friends: User.t(),
|
||||||
recipients_from_activity: [String.t()],
|
recipients_from_activity: [String.t()],
|
||||||
nickname: [String.t()],
|
nickname: [String.t()] | String.t(),
|
||||||
ap_id: [String.t()],
|
ap_id: [String.t()],
|
||||||
order_by: term(),
|
order_by: term(),
|
||||||
select: term(),
|
select: term(),
|
||||||
|
|
|
@ -33,9 +33,15 @@ defp format_query(query_string) do
|
||||||
# Strip the beginning @ off if there is a query
|
# Strip the beginning @ off if there is a query
|
||||||
query_string = String.trim_leading(query_string, "@")
|
query_string = String.trim_leading(query_string, "@")
|
||||||
|
|
||||||
with [name, domain] <- String.split(query_string, "@"),
|
with [name, domain] <- String.split(query_string, "@") do
|
||||||
formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") do
|
encoded_domain =
|
||||||
name <> "@" <> to_string(:idna.encode(formatted_domain))
|
domain
|
||||||
|
|> String.replace(~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "")
|
||||||
|
|> String.to_charlist()
|
||||||
|
|> :idna.encode()
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
name <> "@" <> encoded_domain
|
||||||
else
|
else
|
||||||
_ -> query_string
|
_ -> query_string
|
||||||
end
|
end
|
||||||
|
|
|
@ -770,13 +770,18 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_instance_activities(params) do
|
def fetch_statuses(reading_user, params) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|> Map.put("instance", params["instance"])
|
|
||||||
|
|
||||||
fetch_activities([Pleroma.Constants.as_public()], params, :offset)
|
recipients =
|
||||||
|
user_activities_recipients(%{
|
||||||
|
"godmode" => params["godmode"],
|
||||||
|
"reading_user" => reading_user
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch_activities(recipients, params, :offset)
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -156,10 +156,11 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||||
when not is_nil(in_reply_to) do
|
when not is_nil(in_reply_to) do
|
||||||
in_reply_to_id = prepare_in_reply_to(in_reply_to)
|
in_reply_to_id = prepare_in_reply_to(in_reply_to)
|
||||||
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
|
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
|
||||||
|
depth = (options[:depth] || 0) + 1
|
||||||
|
|
||||||
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
|
if Federator.allowed_thread_distance?(depth) do
|
||||||
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
|
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
|
||||||
%Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
%Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
||||||
object
|
object
|
||||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||||
|
@ -312,7 +313,7 @@ def fix_type(object, options \\ [])
|
||||||
|
|
||||||
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
||||||
when is_binary(reply_id) do
|
when is_binary(reply_id) do
|
||||||
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
|
with true <- Federator.allowed_thread_distance?(options[:depth]),
|
||||||
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
|
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
|
||||||
Map.put(object, "type", "Answer")
|
Map.put(object, "type", "Answer")
|
||||||
else
|
else
|
||||||
|
@ -406,8 +407,7 @@ def handle_incoming(
|
||||||
|
|
||||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||||
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
object = fix_object(object, options)
|
||||||
object = fix_object(data["object"], options)
|
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
to: data["to"],
|
to: data["to"],
|
||||||
|
@ -424,7 +424,20 @@ def handle_incoming(
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityPub.create(params)
|
with {:ok, created_activity} <- ActivityPub.create(params) do
|
||||||
|
reply_depth = (options[:depth] || 0) + 1
|
||||||
|
|
||||||
|
if Federator.allowed_thread_distance?(reply_depth) do
|
||||||
|
for reply_id <- replies(object) do
|
||||||
|
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
|
||||||
|
"id" => reply_id,
|
||||||
|
"depth" => reply_depth
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, created_activity}
|
||||||
|
end
|
||||||
else
|
else
|
||||||
%Activity{} = activity -> {:ok, activity}
|
%Activity{} = activity -> {:ok, activity}
|
||||||
_e -> :error
|
_e -> :error
|
||||||
|
@ -442,7 +455,8 @@ def handle_incoming(
|
||||||
|> fix_addressing
|
|> fix_addressing
|
||||||
|
|
||||||
with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||||
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
reply_depth = (options[:depth] || 0) + 1
|
||||||
|
options = Keyword.put(options, :depth, reply_depth)
|
||||||
object = fix_object(object, options)
|
object = fix_object(object, options)
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
|
@ -903,6 +917,50 @@ def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_r
|
||||||
|
|
||||||
def set_reply_to_uri(obj), do: obj
|
def set_reply_to_uri(obj), do: obj
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
|
||||||
|
Based on Mastodon's ActivityPub::NoteSerializer#replies.
|
||||||
|
"""
|
||||||
|
def set_replies(obj_data) do
|
||||||
|
replies_uris =
|
||||||
|
with limit when limit > 0 <-
|
||||||
|
Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0),
|
||||||
|
%Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do
|
||||||
|
object
|
||||||
|
|> Object.self_replies()
|
||||||
|
|> select([o], fragment("?->>'id'", o.data))
|
||||||
|
|> limit(^limit)
|
||||||
|
|> Repo.all()
|
||||||
|
else
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
|
||||||
|
set_replies(obj_data, replies_uris)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp set_replies(obj, []) do
|
||||||
|
obj
|
||||||
|
end
|
||||||
|
|
||||||
|
defp set_replies(obj, replies_uris) do
|
||||||
|
replies_collection = %{
|
||||||
|
"type" => "Collection",
|
||||||
|
"items" => replies_uris
|
||||||
|
}
|
||||||
|
|
||||||
|
Map.merge(obj, %{"replies" => replies_collection})
|
||||||
|
end
|
||||||
|
|
||||||
|
def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do
|
||||||
|
items
|
||||||
|
end
|
||||||
|
|
||||||
|
def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do
|
||||||
|
items
|
||||||
|
end
|
||||||
|
|
||||||
|
def replies(_), do: []
|
||||||
|
|
||||||
# Prepares the object of an outgoing create activity.
|
# Prepares the object of an outgoing create activity.
|
||||||
def prepare_object(object) do
|
def prepare_object(object) do
|
||||||
object
|
object
|
||||||
|
@ -914,6 +972,7 @@ def prepare_object(object) do
|
||||||
|> prepare_attachments
|
|> prepare_attachments
|
||||||
|> set_conversation
|
|> set_conversation
|
||||||
|> set_reply_to_uri
|
|> set_reply_to_uri
|
||||||
|
|> set_replies
|
||||||
|> strip_internal_fields
|
|> strip_internal_fields
|
||||||
|> strip_internal_tags
|
|> strip_internal_tags
|
||||||
|> set_type
|
|> set_type
|
||||||
|
|
|
@ -45,8 +45,8 @@ def normalize_params(params) do
|
||||||
Map.put(params, "actor", get_ap_id(params["actor"]))
|
Map.put(params, "actor", get_ap_id(params["actor"]))
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec determine_explicit_mentions(map()) :: map()
|
@spec determine_explicit_mentions(map()) :: [any]
|
||||||
def determine_explicit_mentions(%{"tag" => tag} = _) when is_list(tag) do
|
def determine_explicit_mentions(%{"tag" => tag}) when is_list(tag) do
|
||||||
Enum.flat_map(tag, fn
|
Enum.flat_map(tag, fn
|
||||||
%{"type" => "Mention", "href" => href} -> [href]
|
%{"type" => "Mention", "href" => href} -> [href]
|
||||||
_ -> []
|
_ -> []
|
||||||
|
@ -427,7 +427,7 @@ defp fetch_likes(object) do
|
||||||
@doc """
|
@doc """
|
||||||
Updates a follow activity's state (for locked accounts).
|
Updates a follow activity's state (for locked accounts).
|
||||||
"""
|
"""
|
||||||
@spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity} | {:error, any()}
|
@spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity | nil}
|
||||||
def update_follow_state_for_all(
|
def update_follow_state_for_all(
|
||||||
%Activity{data: %{"actor" => actor, "object" => object}} = activity,
|
%Activity{data: %{"actor" => actor, "object" => object}} = activity,
|
||||||
state
|
state
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
alias Pleroma.ModerationLog
|
alias Pleroma.ModerationLog
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.ReportNote
|
alias Pleroma.ReportNote
|
||||||
|
alias Pleroma.Stats
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
@ -98,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read"], admin: true}
|
%{scopes: ["read"], admin: true}
|
||||||
when action in [:config_show, :list_log]
|
when action in [:config_show, :list_log, :stats]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -243,13 +244,15 @@ def user_show(conn, %{"nickname" => nickname}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
||||||
|
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||||
{page, page_size} = page_params(params)
|
{page, page_size} = page_params(params)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_instance_activities(%{
|
ActivityPub.fetch_statuses(nil, %{
|
||||||
"instance" => instance,
|
"instance" => instance,
|
||||||
"limit" => page_size,
|
"limit" => page_size,
|
||||||
"offset" => (page - 1) * page_size
|
"offset" => (page - 1) * page_size,
|
||||||
|
"exclude_reblogs" => !with_reblogs && "true"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -258,6 +261,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||||
|
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
|
||||||
godmode = params["godmode"] == "true" || params["godmode"] == true
|
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||||
|
|
||||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||||
|
@ -266,7 +270,8 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_user_activities(user, nil, %{
|
ActivityPub.fetch_user_activities(user, nil, %{
|
||||||
"limit" => page_size,
|
"limit" => page_size,
|
||||||
"godmode" => godmode
|
"godmode" => godmode,
|
||||||
|
"exclude_reblogs" => !with_reblogs && "true"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -740,6 +745,24 @@ def report_notes_delete(%{assigns: %{user: user}} = conn, %{
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def list_statuses(%{assigns: %{user: admin}} = conn, params) do
|
||||||
|
godmode = params["godmode"] == "true" || params["godmode"] == true
|
||||||
|
local_only = params["local_only"] == "true" || params["local_only"] == true
|
||||||
|
{page, page_size} = page_params(params)
|
||||||
|
|
||||||
|
activities =
|
||||||
|
ActivityPub.fetch_statuses(admin, %{
|
||||||
|
"godmode" => godmode,
|
||||||
|
"local_only" => local_only,
|
||||||
|
"limit" => page_size,
|
||||||
|
"offset" => (page - 1) * page_size
|
||||||
|
})
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|
||||||
|
|> render("index.json", %{activities: activities, as: :activity})
|
||||||
|
end
|
||||||
|
|
||||||
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
||||||
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
|
||||||
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
|
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
|
||||||
|
@ -953,6 +976,13 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
|
||||||
conn |> json("")
|
conn |> json("")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stats(conn, _) do
|
||||||
|
count = Stats.get_status_visibility_count()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json(%{"status_visibility" => count})
|
||||||
|
end
|
||||||
|
|
||||||
def errors(conn, {:error, :not_found}) do
|
def errors(conn, {:error, :not_found}) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|
|
|
@ -18,7 +18,11 @@ defmacro not_empty_string(string) do
|
||||||
|
|
||||||
@spec user(map()) :: {:ok, [User.t()], pos_integer()}
|
@spec user(map()) :: {:ok, [User.t()], pos_integer()}
|
||||||
def user(params \\ %{}) do
|
def user(params \\ %{}) do
|
||||||
query = User.Query.build(params) |> order_by([u], u.nickname)
|
query =
|
||||||
|
params
|
||||||
|
|> Map.drop([:page, :page_size])
|
||||||
|
|> User.Query.build()
|
||||||
|
|> order_by([u], u.nickname)
|
||||||
|
|
||||||
paginated_query =
|
paginated_query =
|
||||||
User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)
|
User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
render_many(opts.activities, __MODULE__, "show.json", opts)
|
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||||
|
|
|
@ -15,13 +15,19 @@ defmodule Pleroma.Web.Federator do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)"
|
@doc """
|
||||||
|
Returns `true` if the distance to target object does not exceed max configured value.
|
||||||
|
Serves to prevent fetching of very long threads, especially useful on smaller instances.
|
||||||
|
Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161).
|
||||||
|
Applies to fetching of both ancestor (reply-to) and child (reply) objects.
|
||||||
|
"""
|
||||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||||
def allowed_incoming_reply_depth?(depth) do
|
def allowed_thread_distance?(distance) do
|
||||||
max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
|
max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
|
||||||
|
|
||||||
if max_replies_depth do
|
if max_distance && max_distance >= 0 do
|
||||||
(depth || 1) <= max_replies_depth
|
# Default depth is 0 (an object has zero distance from itself in its thread)
|
||||||
|
(distance || 0) <= max_distance
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -92,9 +92,9 @@ def raw_nodeinfo do
|
||||||
openRegistrations: Config.get([:instance, :registrations_open]),
|
openRegistrations: Config.get([:instance, :registrations_open]),
|
||||||
usage: %{
|
usage: %{
|
||||||
users: %{
|
users: %{
|
||||||
total: stats.user_count || 0
|
total: Map.get(stats, :user_count, 0)
|
||||||
},
|
},
|
||||||
localPosts: stats.status_count || 0
|
localPosts: Map.get(stats, :status_count, 0)
|
||||||
},
|
},
|
||||||
metadata: %{
|
metadata: %{
|
||||||
nodeName: Config.get([:instance, :name]),
|
nodeName: Config.get([:instance, :name]),
|
||||||
|
|
|
@ -323,7 +323,7 @@ def delete(conn, %{"name" => name}) do
|
||||||
{:ok, _} ->
|
{:ok, _} ->
|
||||||
conn |> json("ok")
|
conn |> json("ok")
|
||||||
|
|
||||||
{:error, _} ->
|
{:error, _, _} ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:internal_server_error)
|
|> put_status(:internal_server_error)
|
||||||
|> json(%{error: "Couldn't delete the pack #{name}"})
|
|> json(%{error: "Couldn't delete the pack #{name}"})
|
||||||
|
|
|
@ -192,6 +192,7 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
put("/statuses/:id", AdminAPIController, :status_update)
|
put("/statuses/:id", AdminAPIController, :status_update)
|
||||||
delete("/statuses/:id", AdminAPIController, :status_delete)
|
delete("/statuses/:id", AdminAPIController, :status_delete)
|
||||||
|
get("/statuses", AdminAPIController, :list_statuses)
|
||||||
|
|
||||||
get("/config", AdminAPIController, :config_show)
|
get("/config", AdminAPIController, :config_show)
|
||||||
post("/config", AdminAPIController, :config_update)
|
post("/config", AdminAPIController, :config_update)
|
||||||
|
@ -201,6 +202,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/moderation_log", AdminAPIController, :list_log)
|
get("/moderation_log", AdminAPIController, :list_log)
|
||||||
|
|
||||||
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
||||||
|
get("/stats", AdminAPIController, :stats)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||||
|
alias Pleroma.Object.Fetcher
|
||||||
|
|
||||||
|
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(
|
||||||
|
%{
|
||||||
|
"op" => "fetch_remote",
|
||||||
|
"id" => id
|
||||||
|
} = args,
|
||||||
|
_job
|
||||||
|
) do
|
||||||
|
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,55 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddCounterCacheTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
create_if_not_exists table(:counter_cache) do
|
||||||
|
add(:name, :string, null: false)
|
||||||
|
add(:count, :bigint, null: false, default: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:counter_cache, [:name]))
|
||||||
|
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION update_status_visibility_counter_cache()
|
||||||
|
RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
BEGIN
|
||||||
|
IF TG_OP = 'INSERT' THEN
|
||||||
|
IF NEW.data->>'type' = 'Create' THEN
|
||||||
|
EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF TG_OP = 'UPDATE' THEN
|
||||||
|
IF (NEW.data->>'type' = 'Create') and (OLD.data->>'type' = 'Create') and activity_visibility(NEW.actor, NEW.recipients, NEW.data) != activity_visibility(OLD.actor, OLD.recipients, OLD.data) THEN
|
||||||
|
EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
|
||||||
|
EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF TG_OP = 'DELETE' THEN
|
||||||
|
IF OLD.data->>'type' = 'Create' THEN
|
||||||
|
EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
|
||||||
|
END IF;
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$
|
||||||
|
LANGUAGE 'plpgsql';
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
CREATE TRIGGER status_visibility_counter_cache_trigger BEFORE INSERT OR UPDATE of recipients, data OR DELETE ON activities
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE update_status_visibility_counter_cache();
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("drop trigger if exists status_visibility_counter_cache_trigger on activities")
|
||||||
|
execute("drop function if exists update_status_visibility_counter_cache()")
|
||||||
|
drop_if_exists(unique_index(:counter_cache, [:name]))
|
||||||
|
drop_if_exists(table(:counter_cache))
|
||||||
|
end
|
||||||
|
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.c836e084.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.dfdeb6eb.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.5f0094e3.js></script></body></html>
|
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.c836e084.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.ae93ea9f.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.30262183.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1581425930672.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.52ac194cbc427f97f06e.js></script><script type=text/javascript src=/static/js/app.f8af8a9b83e330e80903.js></script></body></html>
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.1055039ce3f2fe4dd110.css rel=stylesheet><link href=/static/fontello.1582927362782.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.c5bbd3734647f0cc7eef.js></script><script type=text/javascript src=/static/js/app.128bd8b808a3b5b6da6b.js></script></body></html>
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue