Merge branch 'develop' into tests/mastodon_api_controller.ex
This commit is contained in:
commit
1053319cd6
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -7,6 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Added
|
### Added
|
||||||
- Refreshing poll results for remote polls
|
- Refreshing poll results for remote polls
|
||||||
- Admin API: Add ability to require password reset
|
- Admin API: Add ability to require password reset
|
||||||
|
- Pleroma API: `GET /api/v1/pleroma/subscription_notifications/` to get list of subscription notifications
|
||||||
|
- Pleroma API: `GET /api/v1/pleroma/subscription_notifications/:id` to get a subscription notification
|
||||||
|
- Pleroma API: `POST /api/v1/pleroma/subscription_notifications/clear` to clear all subscription notifications
|
||||||
|
- Pleroma API: `POST /api/v1/pleroma/subscription_notifications/dismiss` to clear a subscription notification
|
||||||
|
- Pleroma API: `DELETE /api/v1/pleroma/subscription_notifications/destroy_multiple` to clear multiple subscription notifications
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
||||||
|
@ -15,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Admin API: Return `total` when querying for reports
|
- Admin API: Return `total` when querying for reports
|
||||||
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
||||||
- Admin API: Return link alongside with token on password reset
|
- Admin API: Return link alongside with token on password reset
|
||||||
|
- Mastodon API: notifications no longer include subscription notifications - they are now served from new endpoints in Pleroma API
|
||||||
### Fixed
|
### Fixed
|
||||||
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
|
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
|
||||||
|
|
||||||
|
@ -45,7 +51,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Improve digest email template
|
- Improve digest email template
|
||||||
– Pagination: (optional) return `total` alongside with `items` when paginating
|
– Pagination: (optional) return `total` alongside with `items` when paginating
|
||||||
- Add `rel="ugc"` to all links in statuses, to prevent SEO spam
|
- Add `rel="ugc"` to all links in statuses, to prevent SEO spam
|
||||||
- ActivityPub: The first page in inboxes/outboxes is no longer embedded.
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Following from Osada
|
- Following from Osada
|
||||||
|
@ -108,6 +113,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
|
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
|
||||||
- Pleroma API: Email change endpoint.
|
- Pleroma API: Email change endpoint.
|
||||||
- Admin API: Added moderation log
|
- Admin API: Added moderation log
|
||||||
|
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
|
||||||
- Web response cache (currently, enabled for ActivityPub)
|
- Web response cache (currently, enabled for ActivityPub)
|
||||||
- Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
|
- Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
|
||||||
- ActivityPub: Add ActivityPub actor's `discoverable` parameter.
|
- ActivityPub: Add ActivityPub actor's `discoverable` parameter.
|
||||||
|
@ -119,6 +125,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- RichMedia: parsers and their order are configured in `rich_media` config.
|
- RichMedia: parsers and their order are configured in `rich_media` config.
|
||||||
- RichMedia: add the rich media ttl based on image expiration time.
|
- RichMedia: add the rich media ttl based on image expiration time.
|
||||||
|
|
||||||
|
## [1.0.7] - 2019-09-26
|
||||||
|
### Fixed
|
||||||
|
- Broken federation on Erlang 22 (previous versions of hackney http client were using an option that got deprecated)
|
||||||
|
### Changed
|
||||||
|
- ActivityPub: The first page in inboxes/outboxes is no longer embedded.
|
||||||
|
|
||||||
## [1.0.6] - 2019-08-14
|
## [1.0.6] - 2019-08-14
|
||||||
### Fixed
|
### Fixed
|
||||||
- MRF: fix use of unserializable keyword lists in describe() implementations
|
- MRF: fix use of unserializable keyword lists in describe() implementations
|
||||||
|
|
|
@ -591,6 +591,8 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.ActivityExpiration, enabled: true
|
config :pleroma, Pleroma.ActivityExpiration, enabled: true
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
|
||||||
|
|
||||||
config :pleroma, :web_cache_ttl,
|
config :pleroma, :web_cache_ttl,
|
||||||
activity_pub: nil,
|
activity_pub: nil,
|
||||||
activity_pub_question: 30_000
|
activity_pub_question: 30_000
|
||||||
|
|
|
@ -2687,6 +2687,42 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Plugs.RemoteIp,
|
||||||
|
type: :group,
|
||||||
|
description: """
|
||||||
|
**If your instance is not behind at least one reverse proxy, you should not enable this plug.**
|
||||||
|
|
||||||
|
`Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
||||||
|
""",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :enabled,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Enable/disable the plug. Defaults to `false`.",
|
||||||
|
suggestions: [true, false]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :headers,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`."
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :proxies,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`."
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reserved,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network)."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :web_cache_ttl,
|
key: :web_cache_ttl,
|
||||||
|
|
|
@ -711,6 +711,7 @@ Compile time settings (need instance reboot):
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
|
|
|
@ -423,6 +423,15 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
|
||||||
* Response: JSON, "ok" and 200 status if the pack was downloaded, or 500 if there were
|
* Response: JSON, "ok" and 200 status if the pack was downloaded, or 500 if there were
|
||||||
errors downloading the pack
|
errors downloading the pack
|
||||||
|
|
||||||
|
## `POST /api/pleroma/emoji/packs/list_from`
|
||||||
|
### Requests the instance to list the packs from another instance
|
||||||
|
* Method `POST`
|
||||||
|
* Authentication: required
|
||||||
|
* Params:
|
||||||
|
* `instance_address`: the address of the instance to download from
|
||||||
|
* Response: JSON with the pack list, same as if the request was made to that instance's
|
||||||
|
list endpoint directly + 200 status
|
||||||
|
|
||||||
## `GET /api/pleroma/emoji/packs/:name/download_shared`
|
## `GET /api/pleroma/emoji/packs/:name/download_shared`
|
||||||
### Requests a local pack from the instance
|
### Requests a local pack from the instance
|
||||||
* Method `GET`
|
* Method `GET`
|
||||||
|
|
|
@ -730,6 +730,8 @@ This will probably take a long time.
|
||||||
|
|
||||||
This is an advanced feature and disabled by default.
|
This is an advanced feature and disabled by default.
|
||||||
|
|
||||||
|
If your instance is behind a reverse proxy you must enable and configure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip).
|
||||||
|
|
||||||
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
||||||
|
|
||||||
* The first element: `scale` (Integer). The time scale in milliseconds.
|
* The first element: `scale` (Integer). The time scale in milliseconds.
|
||||||
|
@ -756,3 +758,16 @@ Available caches:
|
||||||
|
|
||||||
* `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration).
|
* `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration).
|
||||||
* `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds).
|
* `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds).
|
||||||
|
|
||||||
|
## Pleroma.Plugs.RemoteIp
|
||||||
|
|
||||||
|
**If your instance is not behind at least one reverse proxy, you should not enable this plug.**
|
||||||
|
|
||||||
|
`Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
* `enabled` - Enable/disable the plug. Defaults to `false`.
|
||||||
|
* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`.
|
||||||
|
* `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`.
|
||||||
|
* `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network).
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# Installing on Alpine Linux
|
# Installing on Alpine Linux
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide is a step-by-step installation guide for Alpine Linux. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.linode.com/docs/tools-reference/custom-kernels-distros/install-alpine-linux-on-your-linode/#configuration). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -l <username> -s $SHELL -c 'command'` instead.
|
This guide is a step-by-step installation guide for Alpine Linux. The instructions were verified against Alpine v3.10 standard image. You might miss additional dependencies if you use `netboot` instead.
|
||||||
|
|
||||||
|
It assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.linode.com/docs/tools-reference/custom-kernels-distros/install-alpine-linux-on-your-linode/#configuration). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -l <username> -s $SHELL -c 'command'` instead.
|
||||||
|
|
||||||
### Required packages
|
### Required packages
|
||||||
|
|
||||||
|
@ -20,12 +22,13 @@ This guide is a step-by-step installation guide for Alpine Linux. It also assume
|
||||||
|
|
||||||
### Prepare the system
|
### Prepare the system
|
||||||
|
|
||||||
* First make sure to have the community repository enabled:
|
* The community repository must be enabled in `/etc/apk/repositories`. Depending on which version and mirror you use this looks like `http://alpine.42.fr/v3.10/community`. If you autogenerated the mirror during installation:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
echo "https://nl.alpinelinux.org/alpine/latest-stable/community" | sudo tee -a /etc/apk/repository
|
awk 'NR==2' /etc/apk/repositories | sed 's/main/community/' | tee -a /etc/apk/repositories
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
* Then update the system, if not already done:
|
* Then update the system, if not already done:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -77,7 +80,8 @@ sudo rc-update add postgresql
|
||||||
* Add a new system user for the Pleroma service:
|
* Add a new system user for the Pleroma service:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo adduser -S -s /bin/false -h /opt/pleroma -H pleroma
|
sudo addgroup pleroma
|
||||||
|
sudo adduser -S -s /bin/false -h /opt/pleroma -H -G pleroma pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
|
**Note**: To execute a single command as the Pleroma system user, use `sudo -Hu pleroma command`. You can also switch to a shell by using `sudo -Hu pleroma $SHELL`. If you don’t have and want `sudo` on your system, you can use `su` as root user (UID 0) for a single command by using `su -l pleroma -s $SHELL -c 'command'` and `su -l pleroma -s $SHELL` for starting a shell.
|
||||||
|
@ -164,7 +168,26 @@ If that doesn’t work, make sure, that nginx is not already running. If it stil
|
||||||
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
|
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
* Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths)
|
* Before starting nginx edit the configuration and change it to your needs. You must change change `server_name` and the paths to the certificates. You can use `nano` (install with `apk add nano` if missing).
|
||||||
|
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
server_name your.domain;
|
||||||
|
listen 80;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name your.domain;
|
||||||
|
listen 443 ssl http2;
|
||||||
|
...
|
||||||
|
ssl_trusted_certificate /etc/letsencrypt/live/your.domain/chain.pem;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* Enable and start nginx:
|
* Enable and start nginx:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -5,180 +5,179 @@
|
||||||
|
|
||||||
## インストール
|
## インストール
|
||||||
|
|
||||||
このガイドはDebian Stretchを仮定しています。Ubuntu 16.04でも可能です。
|
このガイドはDebian Stretchを利用することを想定しています。Ubuntu 16.04や18.04でもおそらく動作します。また、ユーザはrootもしくはsudoにより管理者権限を持っていることを前提とします。もし、以下の操作をrootユーザで行う場合は、 `sudo` を無視してください。ただし、`sudo -Hu pleroma` のようにユーザを指定している場合には `su <username> -s $SHELL -c 'command'` を代わりに使ってください。
|
||||||
|
|
||||||
### 必要なソフトウェア
|
### 必要なソフトウェア
|
||||||
|
|
||||||
- PostgreSQL 9.6+ (postgresql-contrib-9.6 または他のバージョンの PSQL をインストールしてください)
|
- PostgreSQL 9.6以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
||||||
- Elixir 1.5 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like))。または [asdf](https://github.com/asdf-vm/asdf) を pleroma ユーザーでインストール。
|
- postgresql-contrib 9.6以上 (同上)
|
||||||
- erlang-dev
|
- Elixir 1.5 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
||||||
|
- erlang-dev
|
||||||
- erlang-tools
|
- erlang-tools
|
||||||
- erlang-parsetools
|
- erlang-parsetools
|
||||||
|
- erlang-eldap (LDAP認証を有効化するときのみ必要)
|
||||||
- erlang-ssh
|
- erlang-ssh
|
||||||
- erlang-xmerl (Jessieではバックポートからインストールすること!)
|
- erlang-xmerl
|
||||||
- git
|
- git
|
||||||
- build-essential
|
- build-essential
|
||||||
- openssh
|
|
||||||
- openssl
|
#### このガイドで利用している追加パッケージ
|
||||||
- nginx prefered (Apacheも動くかもしれませんが、誰もテストしていません!)
|
|
||||||
- certbot (または何らかのACME Let's encryptクライアント)
|
- nginx (おすすめです。他のリバースプロキシを使う場合は、参考となる設定をこのリポジトリから探してください)
|
||||||
|
- certbot (または何らかのLet's Encrypt向けACMEクライアント)
|
||||||
|
|
||||||
### システムを準備する
|
### システムを準備する
|
||||||
|
|
||||||
* まずシステムをアップデートしてください。
|
* まずシステムをアップデートしてください。
|
||||||
```
|
```
|
||||||
apt update && apt dist-upgrade
|
sudo apt update
|
||||||
|
sudo apt full-upgrade
|
||||||
```
|
```
|
||||||
|
|
||||||
* 複数のツールとpostgresqlをインストールします。あとで必要になるので。
|
* 上記に挙げたパッケージをインストールしておきます。
|
||||||
```
|
```
|
||||||
apt install git build-essential openssl ssh sudo postgresql-9.6 postgresql-contrib-9.6
|
sudo apt install git build-essential postgresql postgresql-contrib
|
||||||
```
|
```
|
||||||
(postgresqlのバージョンは、あなたのディストロにあわせて変えてください。または、バージョン番号がいらないかもしれません。)
|
|
||||||
|
|
||||||
### ElixirとErlangをインストールします
|
### ElixirとErlangをインストールします
|
||||||
|
|
||||||
* Erlangのリポジトリをダウンロードおよびインストールします。
|
* Erlangのリポジトリをダウンロードおよびインストールします。
|
||||||
```
|
```
|
||||||
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
|
||||||
|
sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
* ElixirとErlangをインストールします、
|
* ElixirとErlangをインストールします、
|
||||||
```
|
```
|
||||||
apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
|
sudo apt update
|
||||||
|
sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pleroma BE (バックエンド) をインストールします
|
### Pleroma BE (バックエンド) をインストールします
|
||||||
|
|
||||||
* 新しいユーザーを作ります。
|
* Pleroma用に新しいユーザーを作ります。
|
||||||
```
|
|
||||||
adduser pleroma
|
|
||||||
```
|
|
||||||
(Give it any password you want, make it STRONG)
|
|
||||||
|
|
||||||
* 新しいユーザーをsudoグループに入れます。
|
|
||||||
```
|
```
|
||||||
usermod -aG sudo pleroma
|
sudo useradd -r -s /bin/false -m -d /var/lib/pleroma -U pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* 新しいユーザーに変身し、ホームディレクトリに移動します。
|
**注意**: Pleromaユーザとして単発のコマンドを実行したい場合はは、`sudo -Hu pleroma command` を使ってください。シェルを使いたい場合は `sudo -Hu pleroma $SHELL`です。もし `sudo` を使わない場合は、rootユーザで `su -l pleroma -s $SHELL -c 'command'` とすることでコマンドを、`su -l pleroma -s $SHELL` とすることでシェルを開始できます。
|
||||||
```
|
|
||||||
su pleroma
|
|
||||||
cd ~
|
|
||||||
```
|
|
||||||
|
|
||||||
* Gitリポジトリをクローンします。
|
* Gitリポジトリをクローンします。
|
||||||
```
|
```
|
||||||
git clone -b master https://git.pleroma.social/pleroma/pleroma
|
sudo mkdir -p /opt/pleroma
|
||||||
|
sudo chown -R pleroma:pleroma /opt/pleroma
|
||||||
|
sudo -Hu pleroma git clone -b master https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* 新しいディレクトリに移動します。
|
* 新しいディレクトリに移動します。
|
||||||
```
|
```
|
||||||
cd pleroma/
|
cd /opt/pleroma
|
||||||
```
|
```
|
||||||
|
|
||||||
* Pleromaが依存するパッケージをインストールします。Hexをインストールしてもよいか聞かれたら、yesを入力してください。
|
* Pleromaが依存するパッケージをインストールします。Hexをインストールしてもよいか聞かれたら、yesを入力してください。
|
||||||
```
|
```
|
||||||
mix deps.get
|
sudo -Hu pleroma mix deps.get
|
||||||
```
|
```
|
||||||
|
|
||||||
* コンフィギュレーションを生成します。
|
* コンフィギュレーションを生成します。
|
||||||
```
|
```
|
||||||
mix pleroma.instance gen
|
sudo -Hu pleroma mix pleroma.instance gen
|
||||||
```
|
```
|
||||||
* rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
|
* rebar3をインストールしてもよいか聞かれたら、yesを入力してください。
|
||||||
* この処理には時間がかかります。私もよく分かりませんが、何らかのコンパイルが行われているようです。
|
* このときにpleromaの一部がコンパイルされるため、この処理には時間がかかります。
|
||||||
* あなたのインスタンスについて、いくつかの質問があります。その回答は `config/generated_config.exs` というコンフィギュレーションファイルに保存されます。
|
* あなたのインスタンスについて、いくつかの質問されます。この質問により `config/generated_config.exs` という設定ファイルが生成されます。
|
||||||
|
|
||||||
**注意**: メディアプロクシを有効にすると回答して、なおかつ、キャッシュのURLは空欄のままにしている場合は、`generated_config.exs` を編集して、`base_url` で始まる行をコメントアウトまたは削除してください。そして、上にある行の `true` の後にあるコンマを消してください。
|
|
||||||
|
|
||||||
* コンフィギュレーションを確認して、もし問題なければ、ファイル名を変更してください。
|
* コンフィギュレーションを確認して、もし問題なければ、ファイル名を変更してください。
|
||||||
```
|
```
|
||||||
mv config/{generated_config.exs,prod.secret.exs}
|
mv config/{generated_config.exs,prod.secret.exs}
|
||||||
```
|
```
|
||||||
|
|
||||||
* これまでのコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
|
* 先程のコマンドで、すでに `config/setup_db.psql` というファイルが作られています。このファイルをもとに、データベースを作成します。
|
||||||
```
|
```
|
||||||
sudo su postgres -c 'psql -f config/setup_db.psql'
|
sudo -Hu pleroma mix pleroma.instance gen
|
||||||
```
|
```
|
||||||
|
|
||||||
* そして、データベースのミグレーションを実行します。
|
* そして、データベースのマイグレーションを実行します。
|
||||||
```
|
```
|
||||||
MIX_ENV=prod mix ecto.migrate
|
sudo -Hu pleroma MIX_ENV=prod mix ecto.migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
* Pleromaを起動できるようになりました。
|
* これでPleromaを起動できるようになりました。
|
||||||
```
|
```
|
||||||
MIX_ENV=prod mix phx.server
|
sudo -Hu pleroma MIX_ENV=prod mix phx.server
|
||||||
```
|
```
|
||||||
|
|
||||||
### インストールを終わらせる
|
### インストールの最終段階
|
||||||
|
|
||||||
あなたの新しいインスタンスを世界に向けて公開するには、nginxまたは何らかのウェブサーバー (プロクシ) を使用する必要があります。また、Pleroma のためにシステムサービスファイルを作成する必要があります。
|
あなたの新しいインスタンスを世界に向けて公開するには、nginx等のWebサーバやプロキシサーバをPleromaの前段に使用する必要があります。また、Pleroma のためにシステムサービスファイルを作成する必要があります。
|
||||||
|
|
||||||
#### Nginx
|
#### Nginx
|
||||||
|
|
||||||
* まだインストールしていないなら、nginxをインストールします。
|
* まだインストールしていないなら、nginxをインストールします。
|
||||||
```
|
```
|
||||||
apt install nginx
|
sudo apt install nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
* SSLをセットアップします。他の方法でもよいですが、ここではcertbotを説明します。
|
* SSLをセットアップします。他の方法でもよいですが、ここではcertbotを説明します。
|
||||||
certbotを使うならば、まずそれをインストールします。
|
certbotを使うならば、まずそれをインストールします。
|
||||||
```
|
```
|
||||||
apt install certbot
|
sudo apt install certbot
|
||||||
```
|
```
|
||||||
そしてセットアップします。
|
そしてセットアップします。
|
||||||
```
|
```
|
||||||
mkdir -p /var/lib/letsencrypt/.well-known
|
sudo mkdir -p /var/lib/letsencrypt/
|
||||||
% certbot certonly --email your@emailaddress --webroot -w /var/lib/letsencrypt/ -d yourdomain
|
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --standalone
|
||||||
```
|
```
|
||||||
もしうまくいかないときは、先にnginxを設定してください。ssl "on" を "off" に変えてから再試行してください。
|
もしうまくいかないときは、nginxが正しく動いていない可能性があります。先にnginxを設定してください。ssl "on" を "off" に変えてから再試行してください。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
* nginxコンフィギュレーションの例をnginxフォルダーにコピーします。
|
* nginxの設定ファイルサンプルをnginxフォルダーにコピーします。
|
||||||
```
|
```
|
||||||
cp /home/pleroma/pleroma/installation/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
|
sudo cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
|
||||||
|
sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
* nginxを起動する前に、コンフィギュレーションを編集してください。例えば、サーバー名、証明書のパスなどを変更する必要があります。
|
* nginxを起動する前に、設定ファイルを編集してください。例えば、サーバー名、証明書のパスなどを変更する必要があります。
|
||||||
* nginxを再起動します。
|
* nginxを再起動します。
|
||||||
```
|
```
|
||||||
systemctl reload nginx.service
|
sudo systemctl enable --now nginx.service
|
||||||
```
|
```
|
||||||
|
|
||||||
|
もし証明書を更新する必要が出てきた場合には、nginxの関連するlocationブロックのコメントアウトを外し、以下のコマンドを動かします。
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo certbot certonly --email <your@emailaddress> -d <yourdomain> --webroot -w /var/lib/letsencrypt/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 他のWebサーバやプロキシ
|
||||||
|
これに関してはサンプルが `/opt/pleroma/installation/` にあるので、探してみてください。
|
||||||
|
|
||||||
#### Systemd サービス
|
#### Systemd サービス
|
||||||
|
|
||||||
* サービスファイルの例をコピーします。
|
* サービスファイルのサンプルをコピーします。
|
||||||
```
|
```
|
||||||
cp /home/pleroma/pleroma/installation/pleroma.service /usr/lib/systemd/system/pleroma.service
|
sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
|
||||||
```
|
```
|
||||||
|
|
||||||
* サービスファイルを変更します。すべてのパスが正しいことを確認してください。また、`[Service]` セクションに以下の行があることを確認してください。
|
* サービスファイルを変更します。すべてのパスが正しいことを確認してください
|
||||||
|
* サービスを有効化し `pleroma.service` を開始してください
|
||||||
```
|
```
|
||||||
Environment="MIX_ENV=prod"
|
sudo systemctl enable --now pleroma.service
|
||||||
```
|
```
|
||||||
|
|
||||||
* `pleroma.service` を enable および start してください。
|
#### 初期ユーザの作成
|
||||||
|
|
||||||
|
新たにインスタンスを作成したら、以下のコマンドにより管理者権限を持った初期ユーザを作成できます。
|
||||||
|
|
||||||
```
|
```
|
||||||
systemctl enable --now pleroma.service
|
sudo -Hu pleroma MIX_ENV=prod mix pleroma.user new <username> <your@emailaddress> --admin
|
||||||
```
|
```
|
||||||
|
|
||||||
#### モデレーターを作る
|
#### その他の設定とカスタマイズ
|
||||||
|
|
||||||
新たにユーザーを作ったら、モデレーター権限を与えたいかもしれません。以下のタスクで可能です。
|
|
||||||
```
|
|
||||||
mix set_moderator username [true|false]
|
|
||||||
```
|
|
||||||
|
|
||||||
モデレーターはすべてのポストを消すことができます。将来的には他のことも可能になるかもしれません。
|
|
||||||
|
|
||||||
#### メディアプロクシを有効にする
|
|
||||||
|
|
||||||
`generate_config` でメディアプロクシを有効にしているなら、すでにメディアプロクシが動作しています。あとから設定を変更したいなら、[How to activate mediaproxy](How-to-activate-mediaproxy) を見てください。
|
|
||||||
|
|
||||||
#### コンフィギュレーションとカスタマイズ
|
|
||||||
|
|
||||||
* [Backup your instance](backup.html)
|
* [Backup your instance](backup.html)
|
||||||
* [Configuration tips](general-tips-for-customizing-pleroma-fe.html)
|
* [Configuration tips](general-tips-for-customizing-pleroma-fe.html)
|
||||||
|
|
|
@ -70,6 +70,7 @@ server {
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
||||||
# and `localhost.` resolves to [::0] on some systems: see issue #930
|
# and `localhost.` resolves to [::0] on some systems: see issue #930
|
||||||
|
|
|
@ -42,7 +42,7 @@ defp loop(state) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def puts_activity(activity) do
|
def puts_activity(activity) do
|
||||||
status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity})
|
status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity})
|
||||||
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
|
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
|
||||||
IO.puts(HtmlSanitizeEx.strip_tags(status.content))
|
IO.puts(HtmlSanitizeEx.strip_tags(status.content))
|
||||||
IO.puts("")
|
IO.puts("")
|
||||||
|
|
|
@ -230,7 +230,6 @@ def get_notified_from_activity(
|
||||||
[]
|
[]
|
||||||
|> Utils.maybe_notify_to_recipients(activity)
|
|> Utils.maybe_notify_to_recipients(activity)
|
||||||
|> Utils.maybe_notify_mentioned_recipients(activity)
|
|> Utils.maybe_notify_mentioned_recipients(activity)
|
||||||
|> Utils.maybe_notify_subscribers(activity)
|
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|
|
||||||
User.get_users_from_set(recipients, local_only)
|
User.get_users_from_set(recipients, local_only)
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.RemoteIp do
|
||||||
|
@moduledoc """
|
||||||
|
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@behaviour Plug
|
||||||
|
|
||||||
|
@headers ~w[
|
||||||
|
forwarded
|
||||||
|
x-forwarded-for
|
||||||
|
x-client-ip
|
||||||
|
x-real-ip
|
||||||
|
]
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/Localhost
|
||||||
|
# https://en.wikipedia.org/wiki/Private_network
|
||||||
|
@reserved ~w[
|
||||||
|
127.0.0.0/8
|
||||||
|
::1/128
|
||||||
|
fc00::/7
|
||||||
|
10.0.0.0/8
|
||||||
|
172.16.0.0/12
|
||||||
|
192.168.0.0/16
|
||||||
|
]
|
||||||
|
|
||||||
|
def init(_), do: nil
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
config = Pleroma.Config.get(__MODULE__, [])
|
||||||
|
|
||||||
|
if Keyword.get(config, :enabled, false) do
|
||||||
|
RemoteIp.call(conn, remote_ip_opts(config))
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp remote_ip_opts(config) do
|
||||||
|
headers = config |> Keyword.get(:headers, @headers) |> MapSet.new()
|
||||||
|
reserved = Keyword.get(config, :reserved, @reserved)
|
||||||
|
|
||||||
|
proxies =
|
||||||
|
config
|
||||||
|
|> Keyword.get(:proxies, [])
|
||||||
|
|> Enum.concat(reserved)
|
||||||
|
|> Enum.map(&InetCidr.parse/1)
|
||||||
|
|
||||||
|
{headers, proxies}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,260 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.SubscriptionNotification do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.Push
|
||||||
|
alias Pleroma.Web.Streamer
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
|
schema "subscription_notifications" do
|
||||||
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%SubscriptionNotification{} = notification, attrs) do
|
||||||
|
cast(notification, attrs, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_query(user, opts \\ []) do
|
||||||
|
query =
|
||||||
|
SubscriptionNotification
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment(
|
||||||
|
"? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
|
||||||
|
a.actor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||||
|
|> join(:left, [n, a], object in Object,
|
||||||
|
on:
|
||||||
|
fragment(
|
||||||
|
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||||
|
object.data,
|
||||||
|
a.data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> preload([n, a, o], activity: {a, object: o})
|
||||||
|
|
||||||
|
if opts[:with_muted] do
|
||||||
|
query
|
||||||
|
else
|
||||||
|
query
|
||||||
|
|> where([n, a], a.actor not in ^user.info.muted_notifications)
|
||||||
|
|> where([n, a], a.actor not in ^user.info.blocks)
|
||||||
|
|> where(
|
||||||
|
[n, a],
|
||||||
|
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
|
||||||
|
)
|
||||||
|
|> join(:left, [n, a], tm in Pleroma.ThreadMute,
|
||||||
|
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
|
||||||
|
)
|
||||||
|
|> where([n, a, o, tm], is_nil(tm.user_id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user(user, opts \\ %{}) do
|
||||||
|
user
|
||||||
|
|> for_user_query(opts)
|
||||||
|
|> Pagination.fetch_paginated(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns notifications for user received since given date.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
|
||||||
|
[%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}]
|
||||||
|
|
||||||
|
iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
|
||||||
|
[]
|
||||||
|
"""
|
||||||
|
@spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
|
||||||
|
def for_user_since(user, date) do
|
||||||
|
user
|
||||||
|
|> for_user_query()
|
||||||
|
|> where([n], n.updated_at > ^date)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_up_to(%{id: user_id} = _user, id) do
|
||||||
|
from(
|
||||||
|
n in SubscriptionNotification,
|
||||||
|
where: n.user_id == ^user_id,
|
||||||
|
where: n.id <= ^id
|
||||||
|
)
|
||||||
|
|> Repo.delete_all([])
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(%{id: user_id} = _user, id) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
n in SubscriptionNotification,
|
||||||
|
where: n.id == ^id,
|
||||||
|
join: activity in assoc(n, :activity),
|
||||||
|
preload: [activity: activity]
|
||||||
|
)
|
||||||
|
|
||||||
|
case Repo.one(query) do
|
||||||
|
%{user_id: ^user_id} = notification ->
|
||||||
|
{:ok, notification}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "Cannot get notification"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear(user) do
|
||||||
|
from(n in SubscriptionNotification, where: n.user_id == ^user.id)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_multiple(%{id: user_id} = _user, ids) do
|
||||||
|
from(n in SubscriptionNotification,
|
||||||
|
where: n.id in ^ids,
|
||||||
|
where: n.user_id == ^user_id
|
||||||
|
)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def dismiss(%{id: user_id} = _user, id) do
|
||||||
|
case Repo.get(SubscriptionNotification, id) do
|
||||||
|
%{user_id: ^user_id} = notification ->
|
||||||
|
Repo.delete(notification)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "Cannot dismiss notification"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
|
||||||
|
case Object.normalize(activity) do
|
||||||
|
%{data: %{"type" => "Answer"}} ->
|
||||||
|
{:ok, []}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
users = get_notified_from_activity(activity)
|
||||||
|
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
|
||||||
|
when type in ["Like", "Announce", "Follow"] do
|
||||||
|
notifications =
|
||||||
|
activity
|
||||||
|
|> get_notified_from_activity()
|
||||||
|
|> Enum.map(&create_notification(activity, &1))
|
||||||
|
|
||||||
|
{:ok, notifications}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_notifications(_), do: {:ok, []}
|
||||||
|
|
||||||
|
# TODO move to sql, too.
|
||||||
|
def create_notification(%Activity{} = activity, %User{} = user) do
|
||||||
|
unless skip?(activity, user) do
|
||||||
|
notification = %SubscriptionNotification{user_id: user.id, activity: activity}
|
||||||
|
{:ok, notification} = Repo.insert(notification)
|
||||||
|
Streamer.stream("user", notification)
|
||||||
|
Streamer.stream("user:subscription_notification", notification)
|
||||||
|
Push.send(notification)
|
||||||
|
notification
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
|
def get_notified_from_activity(
|
||||||
|
%Activity{data: %{"to" => _, "type" => type} = _data} = activity,
|
||||||
|
local_only
|
||||||
|
)
|
||||||
|
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||||
|
[]
|
||||||
|
|> Utils.maybe_notify_subscribers(activity)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> User.get_users_from_set(local_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_notified_from_activity(_, _local_only), do: []
|
||||||
|
|
||||||
|
@spec skip?(Activity.t(), User.t()) :: boolean()
|
||||||
|
def skip?(activity, user) do
|
||||||
|
[
|
||||||
|
:self,
|
||||||
|
:followers,
|
||||||
|
:follows,
|
||||||
|
:non_followers,
|
||||||
|
:non_follows,
|
||||||
|
:recently_followed
|
||||||
|
]
|
||||||
|
|> Enum.any?(&skip?(&1, activity, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
|
||||||
|
def skip?(:self, activity, user) do
|
||||||
|
activity.data["actor"] == user.ap_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(
|
||||||
|
:followers,
|
||||||
|
%{data: %{"actor" => actor}},
|
||||||
|
%{info: %{notification_settings: %{"followers" => false}}} = user
|
||||||
|
) do
|
||||||
|
actor
|
||||||
|
|> User.get_cached_by_ap_id()
|
||||||
|
|> User.following?(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(
|
||||||
|
:non_followers,
|
||||||
|
activity,
|
||||||
|
%{info: %{notification_settings: %{"non_followers" => false}}} = user
|
||||||
|
) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
follower = User.get_cached_by_ap_id(actor)
|
||||||
|
!User.following?(follower, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
|
User.following?(user, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(
|
||||||
|
:non_follows,
|
||||||
|
activity,
|
||||||
|
%{info: %{notification_settings: %{"non_follows" => false}}} = user
|
||||||
|
) do
|
||||||
|
actor = activity.data["actor"]
|
||||||
|
followed = User.get_cached_by_ap_id(actor)
|
||||||
|
!User.following?(user, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do
|
||||||
|
user
|
||||||
|
|> SubscriptionNotification.for_user()
|
||||||
|
|> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1))
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(_, _, _), do: false
|
||||||
|
end
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
alias Pleroma.Upload
|
alias Pleroma.Upload
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
@ -151,6 +152,7 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
|
||||||
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
|
||||||
|
|
||||||
Notification.create_notifications(activity)
|
Notification.create_notifications(activity)
|
||||||
|
SubscriptionNotification.create_notifications(activity)
|
||||||
|
|
||||||
participations =
|
participations =
|
||||||
activity
|
activity
|
||||||
|
|
|
@ -513,7 +513,7 @@ def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("status.json", %{activity: activity})
|
|> render("show.json", %{activity: activity})
|
||||||
else
|
else
|
||||||
true ->
|
true ->
|
||||||
{:param_cast, nil}
|
{:param_cast, nil}
|
||||||
|
@ -537,7 +537,7 @@ def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> render("status.json", %{activity: activity})
|
|> render("show.json", %{activity: activity})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
|
defstruct valid?: true,
|
||||||
|
errors: [],
|
||||||
|
user: nil,
|
||||||
|
params: %{},
|
||||||
|
status: nil,
|
||||||
|
summary: nil,
|
||||||
|
full_payload: nil,
|
||||||
|
attachments: [],
|
||||||
|
in_reply_to: nil,
|
||||||
|
in_reply_to_conversation: nil,
|
||||||
|
visibility: nil,
|
||||||
|
expires_at: nil,
|
||||||
|
poll: nil,
|
||||||
|
emoji: %{},
|
||||||
|
content_html: nil,
|
||||||
|
mentions: [],
|
||||||
|
tags: [],
|
||||||
|
to: [],
|
||||||
|
cc: [],
|
||||||
|
context: nil,
|
||||||
|
sensitive: false,
|
||||||
|
object: nil,
|
||||||
|
preview?: false,
|
||||||
|
changes: %{}
|
||||||
|
|
||||||
|
def create(user, params) do
|
||||||
|
%__MODULE__{user: user}
|
||||||
|
|> put_params(params)
|
||||||
|
|> status()
|
||||||
|
|> summary()
|
||||||
|
|> with_valid(&attachments/1)
|
||||||
|
|> full_payload()
|
||||||
|
|> expires_at()
|
||||||
|
|> poll()
|
||||||
|
|> with_valid(&in_reply_to/1)
|
||||||
|
|> with_valid(&in_reply_to_conversation/1)
|
||||||
|
|> with_valid(&visibility/1)
|
||||||
|
|> content()
|
||||||
|
|> with_valid(&to_and_cc/1)
|
||||||
|
|> with_valid(&context/1)
|
||||||
|
|> sensitive()
|
||||||
|
|> with_valid(&object/1)
|
||||||
|
|> preview?()
|
||||||
|
|> with_valid(&changes/1)
|
||||||
|
|> validate()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_params(draft, params) do
|
||||||
|
params = Map.put_new(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
%__MODULE__{draft | params: params}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp status(%{params: %{"status" => status}} = draft) do
|
||||||
|
%__MODULE__{draft | status: String.trim(status)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp summary(%{params: params} = draft) do
|
||||||
|
%__MODULE__{draft | summary: Map.get(params, "spoiler_text", "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp full_payload(%{status: status, summary: summary} = draft) do
|
||||||
|
full_payload = String.trim(status <> summary)
|
||||||
|
|
||||||
|
case Utils.validate_character_limit(full_payload, draft.attachments) do
|
||||||
|
:ok -> %__MODULE__{draft | full_payload: full_payload}
|
||||||
|
{:error, message} -> add_error(draft, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp attachments(%{params: params} = draft) do
|
||||||
|
attachments = Utils.attachments_from_ids(params)
|
||||||
|
%__MODULE__{draft | attachments: attachments}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp in_reply_to(draft) do
|
||||||
|
case Map.get(draft.params, "in_reply_to_status_id") do
|
||||||
|
"" -> draft
|
||||||
|
nil -> draft
|
||||||
|
id -> %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp in_reply_to_conversation(draft) do
|
||||||
|
in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"])
|
||||||
|
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp visibility(%{params: params} = draft) do
|
||||||
|
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
||||||
|
{visibility, "direct"} when visibility != "direct" ->
|
||||||
|
add_error(draft, dgettext("errors", "The message visibility must be direct"))
|
||||||
|
|
||||||
|
{visibility, _} ->
|
||||||
|
%__MODULE__{draft | visibility: visibility}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp expires_at(draft) do
|
||||||
|
case CommonAPI.check_expiry_date(draft.params["expires_in"]) do
|
||||||
|
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at}
|
||||||
|
{:error, message} -> add_error(draft, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp poll(draft) do
|
||||||
|
case Utils.make_poll_data(draft.params) do
|
||||||
|
{:ok, {poll, poll_emoji}} ->
|
||||||
|
%__MODULE__{draft | poll: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
|
||||||
|
|
||||||
|
{:error, message} ->
|
||||||
|
add_error(draft, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp content(draft) do
|
||||||
|
{content_html, mentions, tags} =
|
||||||
|
Utils.make_content_html(
|
||||||
|
draft.status,
|
||||||
|
draft.attachments,
|
||||||
|
draft.params,
|
||||||
|
draft.visibility
|
||||||
|
)
|
||||||
|
|
||||||
|
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp to_and_cc(draft) do
|
||||||
|
addressed_users =
|
||||||
|
draft.mentions
|
||||||
|
|> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end)
|
||||||
|
|> Utils.get_addressed_users(draft.params["to"])
|
||||||
|
|
||||||
|
{to, cc} =
|
||||||
|
Utils.get_to_and_cc(
|
||||||
|
draft.user,
|
||||||
|
addressed_users,
|
||||||
|
draft.in_reply_to,
|
||||||
|
draft.visibility,
|
||||||
|
draft.in_reply_to_conversation
|
||||||
|
)
|
||||||
|
|
||||||
|
%__MODULE__{draft | to: to, cc: cc}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp context(draft) do
|
||||||
|
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
|
||||||
|
%__MODULE__{draft | context: context}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sensitive(draft) do
|
||||||
|
sensitive = draft.params["sensitive"] || Enum.member?(draft.tags, {"#nsfw", "nsfw"})
|
||||||
|
%__MODULE__{draft | sensitive: sensitive}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp object(draft) do
|
||||||
|
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
|
||||||
|
|
||||||
|
object =
|
||||||
|
Utils.make_note_data(
|
||||||
|
draft.user.ap_id,
|
||||||
|
draft.to,
|
||||||
|
draft.context,
|
||||||
|
draft.content_html,
|
||||||
|
draft.attachments,
|
||||||
|
draft.in_reply_to,
|
||||||
|
draft.tags,
|
||||||
|
draft.summary,
|
||||||
|
draft.cc,
|
||||||
|
draft.sensitive,
|
||||||
|
draft.poll
|
||||||
|
)
|
||||||
|
|> Map.put("emoji", emoji)
|
||||||
|
|
||||||
|
%__MODULE__{draft | object: object}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp preview?(draft) do
|
||||||
|
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false
|
||||||
|
%__MODULE__{draft | preview?: preview?}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp changes(draft) do
|
||||||
|
direct? = draft.visibility == "direct"
|
||||||
|
|
||||||
|
changes =
|
||||||
|
%{
|
||||||
|
to: draft.to,
|
||||||
|
actor: draft.user,
|
||||||
|
context: draft.context,
|
||||||
|
object: draft.object,
|
||||||
|
additional: %{"cc" => draft.cc, "directMessage" => direct?}
|
||||||
|
}
|
||||||
|
|> Utils.maybe_add_list_data(draft.user, draft.visibility)
|
||||||
|
|
||||||
|
%__MODULE__{draft | changes: changes}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_valid(%{valid?: true} = draft, func), do: func.(draft)
|
||||||
|
defp with_valid(draft, _func), do: draft
|
||||||
|
|
||||||
|
defp add_error(draft, message) do
|
||||||
|
%__MODULE__{draft | valid?: false, errors: [message | draft.errors]}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate(%{valid?: true} = draft), do: {:ok, draft}
|
||||||
|
defp validate(%{errors: [message | _]}), do: {:error, message}
|
||||||
|
end
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.ActivityExpiration
|
alias Pleroma.ActivityExpiration
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Emoji
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.ThreadMute
|
alias Pleroma.ThreadMute
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -18,14 +17,11 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
import Pleroma.Web.CommonAPI.Utils
|
import Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
def follow(follower, followed) do
|
def follow(follower, followed) do
|
||||||
|
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||||
|
|
||||||
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||||
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
{:ok, activity} <- ActivityPub.follow(follower, followed),
|
||||||
{:ok, follower, followed} <-
|
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
|
||||||
User.wait_and_refresh(
|
|
||||||
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]),
|
|
||||||
follower,
|
|
||||||
followed
|
|
||||||
) do
|
|
||||||
{:ok, follower, followed, activity}
|
{:ok, follower, followed, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -76,8 +72,7 @@ def delete(activity_id, user) do
|
||||||
{:ok, delete} <- ActivityPub.delete(object) do
|
{:ok, delete} <- ActivityPub.delete(object) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> {:error, dgettext("errors", "Could not delete")}
|
||||||
{:error, dgettext("errors", "Could not delete")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -87,18 +82,16 @@ def repeat(id_or_ap_id, user) do
|
||||||
nil <- Utils.get_existing_announce(user.ap_id, object) do
|
nil <- Utils.get_existing_announce(user.ap_id, object) do
|
||||||
ActivityPub.announce(user, object)
|
ActivityPub.announce(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> {:error, dgettext("errors", "Could not repeat")}
|
||||||
{:error, dgettext("errors", "Could not repeat")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unrepeat(id_or_ap_id, user) do
|
def unrepeat(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do
|
||||||
object <- Object.normalize(activity) do
|
object = Object.normalize(activity)
|
||||||
ActivityPub.unannounce(user, object)
|
ActivityPub.unannounce(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> {:error, dgettext("errors", "Could not unrepeat")}
|
||||||
{:error, dgettext("errors", "Could not unrepeat")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -108,30 +101,23 @@ def favorite(id_or_ap_id, user) do
|
||||||
nil <- Utils.get_existing_like(user.ap_id, object) do
|
nil <- Utils.get_existing_like(user.ap_id, object) do
|
||||||
ActivityPub.like(user, object)
|
ActivityPub.like(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> {:error, dgettext("errors", "Could not favorite")}
|
||||||
{:error, dgettext("errors", "Could not favorite")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfavorite(id_or_ap_id, user) do
|
def unfavorite(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id) do
|
||||||
object <- Object.normalize(activity) do
|
object = Object.normalize(activity)
|
||||||
ActivityPub.unlike(user, object)
|
ActivityPub.unlike(user, object)
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> {:error, dgettext("errors", "Could not unfavorite")}
|
||||||
{:error, dgettext("errors", "Could not unfavorite")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def vote(user, object, choices) do
|
def vote(user, %{data: %{"type" => "Question"}} = object, choices) do
|
||||||
with "Question" <- object.data["type"],
|
with :ok <- validate_not_author(object, user),
|
||||||
{:author, false} <- {:author, object.data["actor"] == user.ap_id},
|
:ok <- validate_existing_votes(user, object),
|
||||||
{:existing_votes, []} <- {:existing_votes, Utils.get_existing_votes(user.ap_id, object)},
|
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
|
||||||
{options, max_count} <- get_options_and_max_count(object),
|
|
||||||
option_count <- Enum.count(options),
|
|
||||||
{:choice_check, {choices, true}} <-
|
|
||||||
{:choice_check, normalize_and_validate_choice_indices(choices, option_count)},
|
|
||||||
{:count_check, true} <- {:count_check, Enum.count(choices) <= max_count} do
|
|
||||||
answer_activities =
|
answer_activities =
|
||||||
Enum.map(choices, fn index ->
|
Enum.map(choices, fn index ->
|
||||||
answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
|
answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
|
||||||
|
@ -150,32 +136,40 @@ def vote(user, object, choices) do
|
||||||
|
|
||||||
object = Object.get_cached_by_ap_id(object.data["id"])
|
object = Object.get_cached_by_ap_id(object.data["id"])
|
||||||
{:ok, answer_activities, object}
|
{:ok, answer_activities, object}
|
||||||
else
|
|
||||||
{:author, _} -> {:error, dgettext("errors", "Poll's author can't vote")}
|
|
||||||
{:existing_votes, _} -> {:error, dgettext("errors", "Already voted")}
|
|
||||||
{:choice_check, {_, false}} -> {:error, dgettext("errors", "Invalid indices")}
|
|
||||||
{:count_check, false} -> {:error, dgettext("errors", "Too many choices")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_options_and_max_count(object) do
|
defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}),
|
||||||
if Map.has_key?(object.data, "anyOf") do
|
do: {:error, dgettext("errors", "Poll's author can't vote")}
|
||||||
{object.data["anyOf"], Enum.count(object.data["anyOf"])}
|
|
||||||
|
defp validate_not_author(_, _), do: :ok
|
||||||
|
|
||||||
|
defp validate_existing_votes(%{ap_id: ap_id}, object) do
|
||||||
|
if Utils.get_existing_votes(ap_id, object) == [] do
|
||||||
|
:ok
|
||||||
else
|
else
|
||||||
{object.data["oneOf"], 1}
|
{:error, dgettext("errors", "Already voted")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp normalize_and_validate_choice_indices(choices, count) do
|
defp get_options_and_max_count(%{data: %{"anyOf" => any_of}}), do: {any_of, Enum.count(any_of)}
|
||||||
Enum.map_reduce(choices, true, fn index, valid ->
|
defp get_options_and_max_count(%{data: %{"oneOf" => one_of}}), do: {one_of, 1}
|
||||||
index = if is_binary(index), do: String.to_integer(index), else: index
|
|
||||||
{index, if(valid, do: index < count, else: valid)}
|
defp normalize_and_validate_choices(choices, object) do
|
||||||
end)
|
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
|
||||||
|
{options, max_count} = get_options_and_max_count(object)
|
||||||
|
count = Enum.count(options)
|
||||||
|
|
||||||
|
with {_, true} <- {:valid_choice, Enum.all?(choices, &(&1 < count))},
|
||||||
|
{_, true} <- {:count_check, Enum.count(choices) <= max_count} do
|
||||||
|
{:ok, options, choices}
|
||||||
|
else
|
||||||
|
{:valid_choice, _} -> {:error, dgettext("errors", "Invalid indices")}
|
||||||
|
{:count_check, _} -> {:error, dgettext("errors", "Too many choices")}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_visibility(_, _, %Participation{}) do
|
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
||||||
{"direct", "direct"}
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
|
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
|
||||||
when visibility in ~w{public unlisted private direct},
|
when visibility in ~w{public unlisted private direct},
|
||||||
|
@ -197,13 +191,13 @@ def get_replied_to_visibility(nil), do: nil
|
||||||
|
|
||||||
def get_replied_to_visibility(activity) do
|
def get_replied_to_visibility(activity) do
|
||||||
with %Object{} = object <- Object.normalize(activity) do
|
with %Object{} = object <- Object.normalize(activity) do
|
||||||
Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
|
Visibility.get_visibility(object)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_expiry_date({:ok, nil} = res), do: res
|
def check_expiry_date({:ok, nil} = res), do: res
|
||||||
|
|
||||||
defp check_expiry_date({:ok, in_seconds}) do
|
def check_expiry_date({:ok, in_seconds}) do
|
||||||
expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
|
expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
|
||||||
|
|
||||||
if ActivityExpiration.expires_late_enough?(expiry) do
|
if ActivityExpiration.expires_late_enough?(expiry) do
|
||||||
|
@ -213,107 +207,36 @@ defp check_expiry_date({:ok, in_seconds}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp check_expiry_date(expiry_str) do
|
def check_expiry_date(expiry_str) do
|
||||||
Ecto.Type.cast(:integer, expiry_str)
|
Ecto.Type.cast(:integer, expiry_str)
|
||||||
|> check_expiry_date()
|
|> check_expiry_date()
|
||||||
end
|
end
|
||||||
|
|
||||||
def post(user, %{"status" => status} = data) do
|
def post(user, %{"status" => _} = data) do
|
||||||
limit = Pleroma.Config.get([:instance, :limit])
|
with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
|
||||||
|
draft.changes
|
||||||
with status <- String.trim(status),
|
|> ActivityPub.create(draft.preview?)
|
||||||
attachments <- attachments_from_ids(data),
|
|> maybe_create_activity_expiration(draft.expires_at)
|
||||||
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
|
||||||
in_reply_to_conversation <- Participation.get(data["in_reply_to_conversation_id"]),
|
|
||||||
{visibility, in_reply_to_visibility} <-
|
|
||||||
get_visibility(data, in_reply_to, in_reply_to_conversation),
|
|
||||||
{_, false} <-
|
|
||||||
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
|
|
||||||
{content_html, mentions, tags} <-
|
|
||||||
make_content_html(
|
|
||||||
status,
|
|
||||||
attachments,
|
|
||||||
data,
|
|
||||||
visibility
|
|
||||||
),
|
|
||||||
mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.ap_id),
|
|
||||||
addressed_users <- get_addressed_users(mentioned_users, data["to"]),
|
|
||||||
{poll, poll_emoji} <- make_poll_data(data),
|
|
||||||
{to, cc} <-
|
|
||||||
get_to_and_cc(user, addressed_users, in_reply_to, visibility, in_reply_to_conversation),
|
|
||||||
context <- make_context(in_reply_to, in_reply_to_conversation),
|
|
||||||
cw <- data["spoiler_text"] || "",
|
|
||||||
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
|
|
||||||
{:ok, expires_at} <- check_expiry_date(data["expires_in"]),
|
|
||||||
full_payload <- String.trim(status <> cw),
|
|
||||||
:ok <- validate_character_limit(full_payload, attachments, limit),
|
|
||||||
object <-
|
|
||||||
make_note_data(
|
|
||||||
user.ap_id,
|
|
||||||
to,
|
|
||||||
context,
|
|
||||||
content_html,
|
|
||||||
attachments,
|
|
||||||
in_reply_to,
|
|
||||||
tags,
|
|
||||||
cw,
|
|
||||||
cc,
|
|
||||||
sensitive,
|
|
||||||
poll
|
|
||||||
),
|
|
||||||
object <- put_emoji(object, full_payload, poll_emoji) do
|
|
||||||
preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
|
||||||
direct? = visibility == "direct"
|
|
||||||
|
|
||||||
result =
|
|
||||||
%{
|
|
||||||
to: to,
|
|
||||||
actor: user,
|
|
||||||
context: context,
|
|
||||||
object: object,
|
|
||||||
additional: %{"cc" => cc, "directMessage" => direct?}
|
|
||||||
}
|
|
||||||
|> maybe_add_list_data(user, visibility)
|
|
||||||
|> ActivityPub.create(preview?)
|
|
||||||
|
|
||||||
if expires_at do
|
|
||||||
with {:ok, activity} <- result do
|
|
||||||
{:ok, _} = ActivityExpiration.create(activity, expires_at)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
result
|
|
||||||
else
|
|
||||||
{:private_to_public, true} ->
|
|
||||||
{:error, dgettext("errors", "The message visibility must be direct")}
|
|
||||||
|
|
||||||
{:error, _} = e ->
|
|
||||||
e
|
|
||||||
|
|
||||||
e ->
|
|
||||||
{:error, e}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# parse and put emoji to object data
|
defp maybe_create_activity_expiration({:ok, activity}, %NaiveDateTime{} = expires_at) do
|
||||||
defp put_emoji(map, text, emojis) do
|
with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
|
||||||
Map.put(
|
{:ok, activity}
|
||||||
map,
|
end
|
||||||
"emoji",
|
|
||||||
Map.merge(Emoji.Formatter.get_emoji_map(text), emojis)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_create_activity_expiration(result, _), do: result
|
||||||
|
|
||||||
# Updates the emojis for a user based on their profile
|
# Updates the emojis for a user based on their profile
|
||||||
def update(user) do
|
def update(user) do
|
||||||
emoji = emoji_from_profile(user)
|
emoji = emoji_from_profile(user)
|
||||||
source_data = user.info |> Map.get(:source_data, {}) |> Map.put("tag", emoji)
|
source_data = user.info |> Map.get(:source_data, %{}) |> Map.put("tag", emoji)
|
||||||
|
|
||||||
user =
|
user =
|
||||||
with {:ok, user} <- User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
|
case User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
|
||||||
user
|
{:ok, user} -> user
|
||||||
else
|
_ -> user
|
||||||
_e -> user
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ActivityPub.update(%{
|
ActivityPub.update(%{
|
||||||
|
@ -328,14 +251,8 @@ def update(user) do
|
||||||
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
with %Activity{
|
with %Activity{
|
||||||
actor: ^user_ap_id,
|
actor: ^user_ap_id,
|
||||||
data: %{
|
data: %{"type" => "Create"},
|
||||||
"type" => "Create"
|
object: %Object{data: %{"type" => "Note"}}
|
||||||
},
|
|
||||||
object: %Object{
|
|
||||||
data: %{
|
|
||||||
"type" => "Note"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
true <- Visibility.is_public?(activity),
|
true <- Visibility.is_public?(activity),
|
||||||
{:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
|
{:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
|
||||||
|
@ -372,51 +289,46 @@ def remove_mute(user, activity) do
|
||||||
def thread_muted?(%{id: nil} = _user, _activity), do: false
|
def thread_muted?(%{id: nil} = _user, _activity), do: false
|
||||||
|
|
||||||
def thread_muted?(user, activity) do
|
def thread_muted?(user, activity) do
|
||||||
with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
|
ThreadMute.check_muted(user.id, activity.data["context"]) != []
|
||||||
false
|
end
|
||||||
else
|
|
||||||
_ -> true
|
def report(user, %{"account_id" => account_id} = data) do
|
||||||
|
with {:ok, account} <- get_reported_account(account_id),
|
||||||
|
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
|
||||||
|
{:ok, statuses} <- get_report_statuses(account, data) do
|
||||||
|
ActivityPub.flag(%{
|
||||||
|
context: Utils.generate_context_id(),
|
||||||
|
actor: user,
|
||||||
|
account: account,
|
||||||
|
statuses: statuses,
|
||||||
|
content: content_html,
|
||||||
|
forward: data["forward"] || false
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def report(user, data) do
|
def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")}
|
||||||
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
|
|
||||||
{:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
|
defp get_reported_account(account_id) do
|
||||||
{:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
|
case User.get_cached_by_id(account_id) do
|
||||||
{:ok, statuses} <- get_report_statuses(account, data),
|
%User{} = account -> {:ok, account}
|
||||||
{:ok, activity} <-
|
_ -> {:error, dgettext("errors", "Account not found")}
|
||||||
ActivityPub.flag(%{
|
|
||||||
context: Utils.generate_context_id(),
|
|
||||||
actor: user,
|
|
||||||
account: account,
|
|
||||||
statuses: statuses,
|
|
||||||
content: content_html,
|
|
||||||
forward: data["forward"] || false
|
|
||||||
}) do
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
{:error, err} -> {:error, err}
|
|
||||||
{:account_id, %{}} -> {:error, dgettext("errors", "Valid `account_id` required")}
|
|
||||||
{:account, nil} -> {:error, dgettext("errors", "Account not found")}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_report_state(activity_id, state) do
|
def update_report_state(activity_id, state) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(activity_id),
|
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
|
||||||
{:ok, activity} <- Utils.update_report_state(activity, state) do
|
Utils.update_report_state(activity, state)
|
||||||
{:ok, activity}
|
|
||||||
else
|
else
|
||||||
nil -> {:error, :not_found}
|
nil -> {:error, :not_found}
|
||||||
{:error, reason} -> {:error, reason}
|
|
||||||
_ -> {:error, dgettext("errors", "Could not update state")}
|
_ -> {:error, dgettext("errors", "Could not update state")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_activity_scope(activity_id, opts \\ %{}) do
|
def update_activity_scope(activity_id, opts \\ %{}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||||
{:ok, activity} <- toggle_sensitive(activity, opts),
|
{:ok, activity} <- toggle_sensitive(activity, opts) do
|
||||||
{:ok, activity} <- set_visibility(activity, opts) do
|
set_visibility(activity, opts)
|
||||||
{:ok, activity}
|
|
||||||
else
|
else
|
||||||
nil -> {:error, :not_found}
|
nil -> {:error, :not_found}
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.CommonAPI.Utils do
|
defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [truthy_param?: 1]
|
||||||
|
|
||||||
alias Calendar.Strftime
|
alias Calendar.Strftime
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
@ -41,14 +42,6 @@ def get_by_id_or_ap_id(id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_replied_to_activity(""), do: nil
|
|
||||||
|
|
||||||
def get_replied_to_activity(id) when not is_nil(id) do
|
|
||||||
Activity.get_by_id(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_replied_to_activity(_), do: nil
|
|
||||||
|
|
||||||
def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
|
def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
|
||||||
attachments_from_ids_descs(ids, desc)
|
attachments_from_ids_descs(ids, desc)
|
||||||
end
|
end
|
||||||
|
@ -159,70 +152,74 @@ def maybe_add_list_data(activity_params, user, {:list, list_id}) do
|
||||||
|
|
||||||
def maybe_add_list_data(activity_params, _, _), do: activity_params
|
def maybe_add_list_data(activity_params, _, _), do: activity_params
|
||||||
|
|
||||||
|
def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data)
|
||||||
|
when is_binary(expires_in) do
|
||||||
|
# In some cases mastofe sends out strings instead of integers
|
||||||
|
data
|
||||||
|
|> put_in(["poll", "expires_in"], String.to_integer(expires_in))
|
||||||
|
|> make_poll_data()
|
||||||
|
end
|
||||||
|
|
||||||
def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
|
def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
|
||||||
when is_list(options) do
|
when is_list(options) do
|
||||||
%{max_expiration: max_expiration, min_expiration: min_expiration} =
|
limits = Pleroma.Config.get([:instance, :poll_limits])
|
||||||
limits = Pleroma.Config.get([:instance, :poll_limits])
|
|
||||||
|
|
||||||
# XXX: There is probably a cleaner way of doing this
|
with :ok <- validate_poll_expiration(expires_in, limits),
|
||||||
try do
|
:ok <- validate_poll_options_amount(options, limits),
|
||||||
# In some cases mastofe sends out strings instead of integers
|
:ok <- validate_poll_options_length(options, limits) do
|
||||||
expires_in = if is_binary(expires_in), do: String.to_integer(expires_in), else: expires_in
|
{option_notes, emoji} =
|
||||||
|
|
||||||
if Enum.count(options) > limits.max_options do
|
|
||||||
raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options"
|
|
||||||
end
|
|
||||||
|
|
||||||
{poll, emoji} =
|
|
||||||
Enum.map_reduce(options, %{}, fn option, emoji ->
|
Enum.map_reduce(options, %{}, fn option, emoji ->
|
||||||
if String.length(option) > limits.max_option_chars do
|
note = %{
|
||||||
raise ArgumentError,
|
"name" => option,
|
||||||
message:
|
"type" => "Note",
|
||||||
"Poll options cannot be longer than #{limits.max_option_chars} characters each"
|
"replies" => %{"type" => "Collection", "totalItems" => 0}
|
||||||
end
|
}
|
||||||
|
|
||||||
{%{
|
{note, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))}
|
||||||
"name" => option,
|
|
||||||
"type" => "Note",
|
|
||||||
"replies" => %{"type" => "Collection", "totalItems" => 0}
|
|
||||||
}, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))}
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
case expires_in do
|
|
||||||
expires_in when expires_in > max_expiration ->
|
|
||||||
raise ArgumentError, message: "Expiration date is too far in the future"
|
|
||||||
|
|
||||||
expires_in when expires_in < min_expiration ->
|
|
||||||
raise ArgumentError, message: "Expiration date is too soon"
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
:noop
|
|
||||||
end
|
|
||||||
|
|
||||||
end_time =
|
end_time =
|
||||||
NaiveDateTime.utc_now()
|
NaiveDateTime.utc_now()
|
||||||
|> NaiveDateTime.add(expires_in)
|
|> NaiveDateTime.add(expires_in)
|
||||||
|> NaiveDateTime.to_iso8601()
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
poll =
|
key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf"
|
||||||
if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do
|
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
|
||||||
%{"type" => "Question", "anyOf" => poll, "closed" => end_time}
|
|
||||||
else
|
|
||||||
%{"type" => "Question", "oneOf" => poll, "closed" => end_time}
|
|
||||||
end
|
|
||||||
|
|
||||||
{poll, emoji}
|
{:ok, {poll, emoji}}
|
||||||
rescue
|
|
||||||
e in ArgumentError -> e.message
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_poll_data(%{"poll" => poll}) when is_map(poll) do
|
def make_poll_data(%{"poll" => poll}) when is_map(poll) do
|
||||||
"Invalid poll"
|
{:error, "Invalid poll"}
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_poll_data(_data) do
|
def make_poll_data(_data) do
|
||||||
{%{}, %{}}
|
{:ok, {%{}, %{}}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_poll_options_amount(options, %{max_options: max_options}) do
|
||||||
|
if Enum.count(options) > max_options do
|
||||||
|
{:error, "Poll can't contain more than #{max_options} options"}
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_poll_options_length(options, %{max_option_chars: max_option_chars}) do
|
||||||
|
if Enum.any?(options, &(String.length(&1) > max_option_chars)) do
|
||||||
|
{:error, "Poll options cannot be longer than #{max_option_chars} characters each"}
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_poll_expiration(expires_in, %{min_expiration: min, max_expiration: max}) do
|
||||||
|
cond do
|
||||||
|
expires_in > max -> {:error, "Expiration date is too far in the future"}
|
||||||
|
expires_in < min -> {:error, "Expiration date is too soon"}
|
||||||
|
true -> :ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_content_html(
|
def make_content_html(
|
||||||
|
@ -234,7 +231,7 @@ def make_content_html(
|
||||||
no_attachment_links =
|
no_attachment_links =
|
||||||
data
|
data
|
||||||
|> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links]))
|
|> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links]))
|
||||||
|> Kernel.in([true, "true"])
|
|> truthy_param?()
|
||||||
|
|
||||||
content_type = get_content_type(data["content_type"])
|
content_type = get_content_type(data["content_type"])
|
||||||
|
|
||||||
|
@ -347,25 +344,25 @@ def make_note_data(
|
||||||
attachments,
|
attachments,
|
||||||
in_reply_to,
|
in_reply_to,
|
||||||
tags,
|
tags,
|
||||||
cw \\ nil,
|
summary \\ nil,
|
||||||
cc \\ [],
|
cc \\ [],
|
||||||
sensitive \\ false,
|
sensitive \\ false,
|
||||||
merge \\ %{}
|
extra_params \\ %{}
|
||||||
) do
|
) do
|
||||||
%{
|
%{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"to" => to,
|
"to" => to,
|
||||||
"cc" => cc,
|
"cc" => cc,
|
||||||
"content" => content_html,
|
"content" => content_html,
|
||||||
"summary" => cw,
|
"summary" => summary,
|
||||||
"sensitive" => !Enum.member?(["false", "False", "0", false], sensitive),
|
"sensitive" => truthy_param?(sensitive),
|
||||||
"context" => context,
|
"context" => context,
|
||||||
"attachment" => attachments,
|
"attachment" => attachments,
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"tag" => Keyword.values(tags) |> Enum.uniq()
|
"tag" => Keyword.values(tags) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|> add_in_reply_to(in_reply_to)
|
|> add_in_reply_to(in_reply_to)
|
||||||
|> Map.merge(merge)
|
|> Map.merge(extra_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_in_reply_to(object, nil), do: object
|
defp add_in_reply_to(object, nil), do: object
|
||||||
|
@ -434,12 +431,14 @@ def confirm_current_password(user, password) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def emoji_from_profile(%{info: _info} = user) do
|
def emoji_from_profile(%User{bio: bio, name: name}) do
|
||||||
(Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name))
|
[bio, name]
|
||||||
|> Enum.map(fn {shortcode, %Emoji{file: url}} ->
|
|> Enum.map(&Emoji.Formatter.get_emoji/1)
|
||||||
|
|> Enum.concat()
|
||||||
|
|> Enum.map(fn {shortcode, %Emoji{file: path}} ->
|
||||||
%{
|
%{
|
||||||
"type" => "Emoji",
|
"type" => "Emoji",
|
||||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
|
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{path}"},
|
||||||
"name" => ":#{shortcode}:"
|
"name" => ":#{shortcode}:"
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
@ -571,15 +570,16 @@ def make_answer_data(%User{ap_id: ap_id}, object, name) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_character_limit(full_payload, attachments, limit) do
|
def validate_character_limit("" = _full_payload, [] = _attachments) do
|
||||||
|
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_character_limit(full_payload, _attachments) do
|
||||||
|
limit = Pleroma.Config.get([:instance, :limit])
|
||||||
length = String.length(full_payload)
|
length = String.length(full_payload)
|
||||||
|
|
||||||
if length < limit do
|
if length < limit do
|
||||||
if length > 0 or Enum.count(attachments) > 0 do
|
:ok
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:error, dgettext("errors", "Cannot post an empty status without attachments")}
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
{:error, dgettext("errors", "The status is over the character limit")}
|
{:error, dgettext("errors", "The status is over the character limit")}
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.ControllerHelper do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||||
@falsy_param_values [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"]
|
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
|
||||||
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
|
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
|
||||||
def truthy_param?(value), do: value not in @falsy_param_values
|
def truthy_param?(value), do: value not in @falsy_param_values
|
||||||
|
|
||||||
|
|
|
@ -97,10 +97,7 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
extra: extra
|
extra: extra
|
||||||
)
|
)
|
||||||
|
|
||||||
# Note: the plug and its configuration is compile-time this can't be upstreamed yet
|
plug(Pleroma.Plugs.RemoteIp)
|
||||||
if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do
|
|
||||||
plug(RemoteIp, proxies: proxies)
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Instrumenter do
|
defmodule Instrumenter do
|
||||||
use Prometheus.PhoenixInstrumenter
|
use Prometheus.PhoenixInstrumenter
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@doc "GET /api/v1/domain_blocks"
|
||||||
|
def index(%{assigns: %{user: %{info: info}}} = conn, _) do
|
||||||
|
json(conn, Map.get(info, :domain_blocks, []))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/domain_blocks"
|
||||||
|
def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||||
|
User.block_domain(blocker, domain)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/domain_blocks"
|
||||||
|
def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||||
|
User.unblock_domain(blocker, domain)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.FilterController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Filter
|
||||||
|
|
||||||
|
@doc "GET /api/v1/filters"
|
||||||
|
def index(%{assigns: %{user: user}} = conn, _) do
|
||||||
|
filters = Filter.get_filters(user)
|
||||||
|
|
||||||
|
render(conn, "filters.json", filters: filters)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/filters"
|
||||||
|
def create(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"phrase" => phrase, "context" => context} = params
|
||||||
|
) do
|
||||||
|
query = %Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
phrase: phrase,
|
||||||
|
context: context,
|
||||||
|
hide: Map.get(params, "irreversible", false),
|
||||||
|
whole_word: Map.get(params, "boolean", true)
|
||||||
|
# expires_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, response} = Filter.create(query)
|
||||||
|
|
||||||
|
render(conn, "filter.json", filter: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/filters/:id"
|
||||||
|
def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
filter = Filter.get(filter_id, user)
|
||||||
|
|
||||||
|
render(conn, "filter.json", filter: filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "PUT /api/v1/filters/:id"
|
||||||
|
def update(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params
|
||||||
|
) do
|
||||||
|
query = %Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: filter_id,
|
||||||
|
phrase: phrase,
|
||||||
|
context: context,
|
||||||
|
hide: Map.get(params, "irreversible", nil),
|
||||||
|
whole_word: Map.get(params, "boolean", true)
|
||||||
|
# expires_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, response} = Filter.update(query)
|
||||||
|
render(conn, "filter.json", filter: response)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/filters/:id"
|
||||||
|
def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
query = %Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: filter_id
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, _} = Filter.delete(query)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
|
||||||
|
plug(:assign_follower when action != :index)
|
||||||
|
|
||||||
|
action_fallback(:errors)
|
||||||
|
|
||||||
|
@doc "GET /api/v1/follow_requests"
|
||||||
|
def index(%{assigns: %{user: followed}} = conn, _params) do
|
||||||
|
follow_requests = User.get_follow_requests(followed)
|
||||||
|
|
||||||
|
render(conn, "accounts.json", for: followed, users: follow_requests, as: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/follow_requests/:id/authorize"
|
||||||
|
def authorize(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
|
||||||
|
with {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
|
render(conn, "relationship.json", user: followed, target: follower)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/follow_requests/:id/reject"
|
||||||
|
def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
|
||||||
|
with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
|
render(conn, "relationship.json", user: followed, target: follower)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_follower(%{params: %{"id" => id}} = conn, _) do
|
||||||
|
case User.get_cached_by_id(id) do
|
||||||
|
%User{} = follower -> assign(conn, :follower, follower)
|
||||||
|
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp errors(conn, {:error, message}) do
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{error: message})
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper,
|
import Pleroma.Web.ControllerHelper,
|
||||||
only: [json_response: 3, add_link_headers: 2, add_link_headers: 3]
|
only: [json_response: 3, add_link_headers: 2, truthy_param?: 1]
|
||||||
|
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
@ -14,13 +14,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Filter
|
|
||||||
alias Pleroma.HTTP
|
alias Pleroma.HTTP
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.ScheduledActivity
|
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
@ -30,51 +28,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.MastodonAPI.AccountView
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
alias Pleroma.Web.MastodonAPI.AppView
|
alias Pleroma.Web.MastodonAPI.AppView
|
||||||
alias Pleroma.Web.MastodonAPI.ConversationView
|
alias Pleroma.Web.MastodonAPI.ConversationView
|
||||||
alias Pleroma.Web.MastodonAPI.FilterView
|
|
||||||
alias Pleroma.Web.MastodonAPI.ListView
|
alias Pleroma.Web.MastodonAPI.ListView
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonView
|
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||||
alias Pleroma.Web.MastodonAPI.ReportView
|
alias Pleroma.Web.MastodonAPI.ReportView
|
||||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
alias Pleroma.Web.OAuth.Scopes
|
alias Pleroma.Web.OAuth.Scopes
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
alias Pleroma.Web.RichMedia
|
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
|
|
||||||
alias Pleroma.Web.ControllerHelper
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
@rate_limited_relations_actions ~w(follow unfollow)a
|
@rate_limited_relations_actions ~w(follow unfollow)a
|
||||||
|
|
||||||
@rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
|
|
||||||
post_status delete_status)a
|
|
||||||
|
|
||||||
plug(
|
|
||||||
RateLimiter,
|
|
||||||
{:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
|
|
||||||
when action in ~w(reblog_status unreblog_status)a
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
|
||||||
RateLimiter,
|
|
||||||
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
|
||||||
when action in ~w(fav_status unfav_status)a
|
|
||||||
)
|
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
RateLimiter,
|
RateLimiter,
|
||||||
{:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
|
{:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
|
plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
|
||||||
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
|
|
||||||
plug(RateLimiter, :app_account_creation when action == :account_register)
|
plug(RateLimiter, :app_account_creation when action == :account_register)
|
||||||
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||||
plug(RateLimiter, :password_reset when action == :password_reset)
|
plug(RateLimiter, :password_reset when action == :password_reset)
|
||||||
|
@ -157,7 +133,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
]
|
]
|
||||||
|> Enum.reduce(%{}, fn key, acc ->
|
|> Enum.reduce(%{}, fn key, acc ->
|
||||||
add_if_present(acc, params, to_string(key), key, fn value ->
|
add_if_present(acc, params, to_string(key), key, fn value ->
|
||||||
{:ok, ControllerHelper.truthy_param?(value)}
|
{:ok, truthy_param?(value)}
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|> add_if_present(params, "default_scope", :default_scope)
|
|> add_if_present(params, "default_scope", :default_scope)
|
||||||
|
@ -345,43 +321,6 @@ def custom_emojis(conn, _params) do
|
||||||
json(conn, mastodon_emoji)
|
json(conn, mastodon_emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|
||||||
|> Map.put("blocking_user", user)
|
|
||||||
|> Map.put("muting_user", user)
|
|
||||||
|> Map.put("user", user)
|
|
||||||
|
|
||||||
activities =
|
|
||||||
[user.ap_id | user.following]
|
|
||||||
|> ActivityPub.fetch_activities(params)
|
|
||||||
|> Enum.reverse()
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(activities)
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
local_only = params["local"] in [true, "True", "true", "1"]
|
|
||||||
|
|
||||||
activities =
|
|
||||||
params
|
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|
||||||
|> Map.put("local_only", local_only)
|
|
||||||
|> Map.put("blocking_user", user)
|
|
||||||
|> Map.put("muting_user", user)
|
|
||||||
|> ActivityPub.fetch_public_activities()
|
|
||||||
|> Enum.reverse()
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(activities, %{"local" => local_only})
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|
||||||
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_cached_by_nickname_or_id(params["id"], for: reading_user) do
|
with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
|
||||||
params =
|
params =
|
||||||
|
@ -401,80 +340,6 @@ def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.put("type", "Create")
|
|
||||||
|> Map.put("blocking_user", user)
|
|
||||||
|> Map.put("user", user)
|
|
||||||
|> Map.put(:visibility, "direct")
|
|
||||||
|
|
||||||
activities =
|
|
||||||
[user.ap_id]
|
|
||||||
|> ActivityPub.fetch_activities_query(params)
|
|
||||||
|> Pagination.fetch_paginated(params)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(activities)
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|
|
||||||
limit = 100
|
|
||||||
|
|
||||||
activities =
|
|
||||||
ids
|
|
||||||
|> Enum.take(limit)
|
|
||||||
|> Activity.all_by_ids_with_object()
|
|
||||||
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json", activities: activities, for: user, as: :activity)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
|
||||||
activities <-
|
|
||||||
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
|
||||||
"blocking_user" => user,
|
|
||||||
"user" => user,
|
|
||||||
"exclude_id" => activity.id
|
|
||||||
}),
|
|
||||||
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
|
|
||||||
result = %{
|
|
||||||
ancestors:
|
|
||||||
StatusView.render("index.json",
|
|
||||||
for: user,
|
|
||||||
activities: grouped_activities[true] || [],
|
|
||||||
as: :activity
|
|
||||||
)
|
|
||||||
|> Enum.reverse(),
|
|
||||||
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
|
|
||||||
descendants:
|
|
||||||
StatusView.render("index.json",
|
|
||||||
for: user,
|
|
||||||
activities: grouped_activities[false] || [],
|
|
||||||
as: :activity
|
|
||||||
)
|
|
||||||
|> Enum.reverse()
|
|
||||||
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
|
|
||||||
}
|
|
||||||
|
|
||||||
json(conn, result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
|
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||||
|
@ -525,193 +390,6 @@ def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choic
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
|
||||||
conn
|
|
||||||
|> add_link_headers(scheduled_activities)
|
|
||||||
|> put_view(ScheduledActivityView)
|
|
||||||
|> render("index.json", %{scheduled_activities: scheduled_activities})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
|
||||||
with %ScheduledActivity{} = scheduled_activity <-
|
|
||||||
ScheduledActivity.get(user, scheduled_activity_id) do
|
|
||||||
conn
|
|
||||||
|> put_view(ScheduledActivityView)
|
|
||||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
|
||||||
else
|
|
||||||
_ -> {:error, :not_found}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_scheduled_status(
|
|
||||||
%{assigns: %{user: user}} = conn,
|
|
||||||
%{"id" => scheduled_activity_id} = params
|
|
||||||
) do
|
|
||||||
with %ScheduledActivity{} = scheduled_activity <-
|
|
||||||
ScheduledActivity.get(user, scheduled_activity_id),
|
|
||||||
{:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
|
||||||
conn
|
|
||||||
|> put_view(ScheduledActivityView)
|
|
||||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
|
||||||
else
|
|
||||||
nil -> {:error, :not_found}
|
|
||||||
error -> error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
|
||||||
with %ScheduledActivity{} = scheduled_activity <-
|
|
||||||
ScheduledActivity.get(user, scheduled_activity_id),
|
|
||||||
{:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
|
||||||
conn
|
|
||||||
|> put_view(ScheduledActivityView)
|
|
||||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
|
||||||
else
|
|
||||||
nil -> {:error, :not_found}
|
|
||||||
error -> error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|
|
||||||
|
|
||||||
scheduled_at = params["scheduled_at"]
|
|
||||||
|
|
||||||
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
|
|
||||||
with {:ok, scheduled_activity} <-
|
|
||||||
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
|
||||||
conn
|
|
||||||
|> put_view(ScheduledActivityView)
|
|
||||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
|
||||||
end
|
|
||||||
else
|
|
||||||
params = Map.drop(params, ["scheduled_at"])
|
|
||||||
|
|
||||||
case CommonAPI.post(user, params) do
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:unprocessable_entity)
|
|
||||||
|> json(%{error: message})
|
|
||||||
|
|
||||||
{:ok, activity} ->
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{
|
|
||||||
activity: activity,
|
|
||||||
for: user,
|
|
||||||
as: :activity,
|
|
||||||
with_direct_conversation_id: true
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
|
||||||
json(conn, %{})
|
|
||||||
else
|
|
||||||
_e -> render_error(conn, :forbidden, "Can't delete this post")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|
||||||
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
|
||||||
%Activity{} = announce <- Activity.normalize(announce.data) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: announce, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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),
|
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|
||||||
with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
|
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|
||||||
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
|
|
||||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|
||||||
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|
||||||
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
|
||||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
|
||||||
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
|
||||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
|
||||||
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
activity = Activity.get_by_id(id)
|
|
||||||
|
|
||||||
with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
activity = Activity.get_by_id(id)
|
|
||||||
|
|
||||||
with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
targets = User.get_all_by_ids(List.wrap(id))
|
targets = User.get_all_by_ids(List.wrap(id))
|
||||||
|
|
||||||
|
@ -778,83 +456,6 @@ def get_mascot(%{assigns: %{user: user}} = conn, _params) do
|
||||||
json(conn, mascot)
|
json(conn, mascot)
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
|
||||||
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
|
||||||
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
|
|
||||||
q = from(u in User, where: u.ap_id in ^likes)
|
|
||||||
|
|
||||||
users =
|
|
||||||
Repo.all(q)
|
|
||||||
|> Enum.filter(&(not User.blocks?(user, &1)))
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("accounts.json", %{for: user, users: users, as: :user})
|
|
||||||
else
|
|
||||||
{:visible, false} -> {:error, :not_found}
|
|
||||||
_ -> json(conn, [])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
|
||||||
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
|
||||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
|
|
||||||
q = from(u in User, where: u.ap_id in ^announces)
|
|
||||||
|
|
||||||
users =
|
|
||||||
Repo.all(q)
|
|
||||||
|> Enum.filter(&(not User.blocks?(user, &1)))
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("accounts.json", %{for: user, users: users, as: :user})
|
|
||||||
else
|
|
||||||
{:visible, false} -> {:error, :not_found}
|
|
||||||
_ -> json(conn, [])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
|
||||||
local_only = params["local"] in [true, "True", "true", "1"]
|
|
||||||
|
|
||||||
tags =
|
|
||||||
[params["tag"], params["any"]]
|
|
||||||
|> List.flatten()
|
|
||||||
|> Enum.uniq()
|
|
||||||
|> Enum.filter(& &1)
|
|
||||||
|> Enum.map(&String.downcase(&1))
|
|
||||||
|
|
||||||
tag_all =
|
|
||||||
params["all"] ||
|
|
||||||
[]
|
|
||||||
|> Enum.map(&String.downcase(&1))
|
|
||||||
|
|
||||||
tag_reject =
|
|
||||||
params["none"] ||
|
|
||||||
[]
|
|
||||||
|> Enum.map(&String.downcase(&1))
|
|
||||||
|
|
||||||
activities =
|
|
||||||
params
|
|
||||||
|> Map.put("type", "Create")
|
|
||||||
|> Map.put("local_only", local_only)
|
|
||||||
|> Map.put("blocking_user", user)
|
|
||||||
|> Map.put("muting_user", user)
|
|
||||||
|> Map.put("user", user)
|
|
||||||
|> Map.put("tag", tags)
|
|
||||||
|> Map.put("tag_all", tag_all)
|
|
||||||
|> Map.put("tag_reject", tag_reject)
|
|
||||||
|> ActivityPub.fetch_public_activities()
|
|
||||||
|> Enum.reverse()
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> add_link_headers(activities, %{"local" => local_only})
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|
||||||
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_cached_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
|
||||||
|
@ -889,42 +490,6 @@ def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
|
|
||||||
follow_requests = User.get_follow_requests(followed)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
|
||||||
with %User{} = follower <- User.get_cached_by_id(id),
|
|
||||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
|
||||||
conn
|
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("relationship.json", %{user: followed, target: follower})
|
|
||||||
else
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(%{error: message})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
|
||||||
with %User{} = follower <- User.get_cached_by_id(id),
|
|
||||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
|
||||||
conn
|
|
||||||
|> put_view(AccountView)
|
|
||||||
|> render("relationship.json", %{user: followed, target: follower})
|
|
||||||
else
|
|
||||||
{:error, message} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> json(%{error: message})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
|
with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},
|
||||||
{_, true} <- {:followed, follower.id != followed.id},
|
{_, true} <- {:followed, follower.id != followed.id},
|
||||||
|
@ -1054,20 +619,6 @@ def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
|
|
||||||
json(conn, info.domain_blocks || [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
|
||||||
User.block_domain(blocker, domain)
|
|
||||||
json(conn, %{})
|
|
||||||
end
|
|
||||||
|
|
||||||
def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
|
||||||
User.unblock_domain(blocker, domain)
|
|
||||||
json(conn, %{})
|
|
||||||
end
|
|
||||||
|
|
||||||
def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %User{} = subscription_target <- User.get_cached_by_id(id),
|
with %User{} = subscription_target <- User.get_cached_by_id(id),
|
||||||
{:ok, subscription_target} = User.subscribe(user, subscription_target) do
|
{:ok, subscription_target} = User.subscribe(user, subscription_target) do
|
||||||
|
@ -1165,31 +716,6 @@ def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
|
||||||
|> render("index.json", %{lists: lists})
|
|> render("index.json", %{lists: lists})
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|
|
||||||
with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
|
|
||||||
params =
|
|
||||||
params
|
|
||||||
|> Map.put("type", "Create")
|
|
||||||
|> Map.put("blocking_user", user)
|
|
||||||
|> Map.put("user", user)
|
|
||||||
|> Map.put("muting_user", user)
|
|
||||||
|
|
||||||
# we must filter the following list for the user to avoid leaking statuses the user
|
|
||||||
# does not actually have permission to see (for more info, peruse security issue #270).
|
|
||||||
activities =
|
|
||||||
following
|
|
||||||
|> Enum.filter(fn x -> x in user.following end)
|
|
||||||
|> ActivityPub.fetch_activities_bounded(following, params)
|
|
||||||
|> Enum.reverse()
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|
||||||
else
|
|
||||||
_e -> render_error(conn, :forbidden, "Error.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
token = get_session(conn, :oauth_token)
|
token = get_session(conn, :oauth_token)
|
||||||
|
|
||||||
|
@ -1368,62 +894,8 @@ def empty_array(conn, _) do
|
||||||
json(conn, [])
|
json(conn, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_filters(%{assigns: %{user: user}} = conn, _) do
|
def empty_object(conn, _) do
|
||||||
filters = Filter.get_filters(user)
|
Logger.debug("Unimplemented, returning an empty object")
|
||||||
res = FilterView.render("filters.json", filters: filters)
|
|
||||||
json(conn, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_filter(
|
|
||||||
%{assigns: %{user: user}} = conn,
|
|
||||||
%{"phrase" => phrase, "context" => context} = params
|
|
||||||
) do
|
|
||||||
query = %Filter{
|
|
||||||
user_id: user.id,
|
|
||||||
phrase: phrase,
|
|
||||||
context: context,
|
|
||||||
hide: Map.get(params, "irreversible", false),
|
|
||||||
whole_word: Map.get(params, "boolean", true)
|
|
||||||
# expires_at
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, response} = Filter.create(query)
|
|
||||||
res = FilterView.render("filter.json", filter: response)
|
|
||||||
json(conn, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
|
||||||
filter = Filter.get(filter_id, user)
|
|
||||||
res = FilterView.render("filter.json", filter: filter)
|
|
||||||
json(conn, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_filter(
|
|
||||||
%{assigns: %{user: user}} = conn,
|
|
||||||
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params
|
|
||||||
) do
|
|
||||||
query = %Filter{
|
|
||||||
user_id: user.id,
|
|
||||||
filter_id: filter_id,
|
|
||||||
phrase: phrase,
|
|
||||||
context: context,
|
|
||||||
hide: Map.get(params, "irreversible", nil),
|
|
||||||
whole_word: Map.get(params, "boolean", true)
|
|
||||||
# expires_at
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, response} = Filter.update(query)
|
|
||||||
res = FilterView.render("filter.json", filter: response)
|
|
||||||
json(conn, res)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
|
||||||
query = %Filter{
|
|
||||||
user_id: user.id,
|
|
||||||
filter_id: filter_id
|
|
||||||
}
|
|
||||||
|
|
||||||
{:ok, _} = Filter.delete(query)
|
|
||||||
json(conn, %{})
|
json(conn, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1474,21 +946,6 @@ defp fetch_suggestion_id(attrs) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
|
||||||
@deprecated "https://github.com/tootsuite/mastodon/pull/11213"
|
|
||||||
def status_card(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
|
||||||
data = RichMedia.Helpers.fetch_data_for_activity(activity)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("card.json", data)
|
|
||||||
else
|
|
||||||
_e -> {:error, :not_found}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reports(%{assigns: %{user: user}} = conn, params) do
|
def reports(%{assigns: %{user: user}} = conn, params) do
|
||||||
case CommonAPI.report(user, params) do
|
case CommonAPI.report(user, params) do
|
||||||
{:ok, activity} ->
|
{:ok, activity} ->
|
||||||
|
@ -1597,15 +1054,15 @@ def account_confirmation_resend(conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp try_render(conn, target, params)
|
def try_render(conn, target, params)
|
||||||
when is_binary(target) do
|
when is_binary(target) do
|
||||||
case render(conn, target, params) do
|
case render(conn, target, params) do
|
||||||
nil -> render_error(conn, :not_implemented, "Can't display this activity")
|
nil -> render_error(conn, :not_implemented, "Can't display this activity")
|
||||||
res -> res
|
res -> res
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp try_render(conn, _, _) do
|
def try_render(conn, _, _) do
|
||||||
render_error(conn, :not_implemented, "Can't display this activity")
|
render_error(conn, :not_implemented, "Can't display this activity")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
|
|
||||||
|
plug(:assign_scheduled_activity when action != :index)
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
@doc "GET /api/v1/scheduled_statuses"
|
||||||
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||||
|
conn
|
||||||
|
|> add_link_headers(scheduled_activities)
|
||||||
|
|> render("index.json", scheduled_activities: scheduled_activities)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/scheduled_statuses/:id"
|
||||||
|
def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do
|
||||||
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "PUT /api/v1/scheduled_statuses/:id"
|
||||||
|
def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do
|
||||||
|
with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||||
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/scheduled_statuses/:id"
|
||||||
|
def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do
|
||||||
|
with {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
||||||
|
render(conn, "show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do
|
||||||
|
case ScheduledActivity.get(user, id) do
|
||||||
|
%ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)
|
||||||
|
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,274 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3]
|
||||||
|
|
||||||
|
require Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Bookmark
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Plugs.RateLimiter
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
|
||||||
|
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
|
||||||
|
|
||||||
|
plug(
|
||||||
|
RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
|
||||||
|
when action in ~w(reblog unreblog)a
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(
|
||||||
|
RateLimiter,
|
||||||
|
{:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
|
||||||
|
when action in ~w(favourite unfavourite)a
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
|
||||||
|
|
||||||
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
GET `/api/v1/statuses?ids[]=1&ids[]=2`
|
||||||
|
|
||||||
|
`ids` query param is required
|
||||||
|
"""
|
||||||
|
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|
||||||
|
limit = 100
|
||||||
|
|
||||||
|
activities =
|
||||||
|
ids
|
||||||
|
|> Enum.take(limit)
|
||||||
|
|> Activity.all_by_ids_with_object()
|
||||||
|
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
||||||
|
|
||||||
|
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
POST /api/v1/statuses
|
||||||
|
|
||||||
|
Creates a scheduled status when `scheduled_at` param is present and it's far enough
|
||||||
|
"""
|
||||||
|
def create(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"status" => _, "scheduled_at" => scheduled_at} = params
|
||||||
|
) do
|
||||||
|
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
|
||||||
|
if ScheduledActivity.far_enough?(scheduled_at) do
|
||||||
|
with {:ok, scheduled_activity} <-
|
||||||
|
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", scheduled_activity: scheduled_activity)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
create(conn, Map.drop(params, ["scheduled_at"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
POST /api/v1/statuses
|
||||||
|
|
||||||
|
Creates a regular status
|
||||||
|
"""
|
||||||
|
def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
|
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
|
||||||
|
|
||||||
|
with {:ok, activity} <- CommonAPI.post(user, params) do
|
||||||
|
try_render(conn, "show.json",
|
||||||
|
activity: activity,
|
||||||
|
for: user,
|
||||||
|
as: :activity,
|
||||||
|
with_direct_conversation_id: true
|
||||||
|
)
|
||||||
|
else
|
||||||
|
{:error, message} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:unprocessable_entity)
|
||||||
|
|> json(%{error: message})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do
|
||||||
|
create(conn, Map.put(params, "status", ""))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id"
|
||||||
|
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "DELETE /api/v1/statuses/:id"
|
||||||
|
def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||||
|
json(conn, %{})
|
||||||
|
else
|
||||||
|
_e -> render_error(conn, :forbidden, "Can't delete this post")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/reblog"
|
||||||
|
def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
||||||
|
%Activity{} = announce <- Activity.normalize(announce.data) do
|
||||||
|
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unreblog"
|
||||||
|
def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
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
|
||||||
|
try_render(conn, "show.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/favourite"
|
||||||
|
def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
|
||||||
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unfavourite"
|
||||||
|
def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
|
||||||
|
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/pin"
|
||||||
|
def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unpin"
|
||||||
|
def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/bookmark"
|
||||||
|
def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
|
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unbookmark"
|
||||||
|
def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
|
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/mute"
|
||||||
|
def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
{:ok, activity} <- CommonAPI.add_mute(user, activity) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "POST /api/v1/statuses/:id/unmute"
|
||||||
|
def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
|
{:ok, activity} <- CommonAPI.remove_mute(user, activity) do
|
||||||
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/card"
|
||||||
|
@deprecated "https://github.com/tootsuite/mastodon/pull/11213"
|
||||||
|
def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||||
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||||
|
render(conn, "card.json", data)
|
||||||
|
else
|
||||||
|
_ -> render_error(conn, :not_found, "Record not found")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/favourited_by"
|
||||||
|
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
||||||
|
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
|
||||||
|
users =
|
||||||
|
User
|
||||||
|
|> Ecto.Query.where([u], u.ap_id in ^likes)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(&(not User.blocks?(user, &1)))
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("accounts.json", for: user, users: users, as: :user)
|
||||||
|
else
|
||||||
|
{:visible, false} -> {:error, :not_found}
|
||||||
|
_ -> json(conn, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/reblogged_by"
|
||||||
|
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
|
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
||||||
|
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
|
||||||
|
users =
|
||||||
|
User
|
||||||
|
|> Ecto.Query.where([u], u.ap_id in ^announces)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(&(not User.blocks?(user, &1)))
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(AccountView)
|
||||||
|
|> render("accounts.json", for: user, users: users, as: :user)
|
||||||
|
else
|
||||||
|
{:visible, false} -> {:error, :not_found}
|
||||||
|
_ -> json(conn, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "GET /api/v1/statuses/:id/context"
|
||||||
|
def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
with %Activity{} = activity <- Activity.get_by_id(id) do
|
||||||
|
activities =
|
||||||
|
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||||
|
"blocking_user" => user,
|
||||||
|
"user" => user,
|
||||||
|
"exclude_id" => activity.id
|
||||||
|
})
|
||||||
|
|
||||||
|
render(conn, "context.json", activity: activity, activities: activities, user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,136 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.TimelineController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper,
|
||||||
|
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
|
||||||
|
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
|
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/home
|
||||||
|
def home(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|
||||||
|
recipients = [user.ap_id | user.following]
|
||||||
|
|
||||||
|
activities =
|
||||||
|
recipients
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities)
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/direct
|
||||||
|
def direct(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|> Map.put(:visibility, "direct")
|
||||||
|
|
||||||
|
activities =
|
||||||
|
[user.ap_id]
|
||||||
|
|> ActivityPub.fetch_activities_query(params)
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities)
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/public
|
||||||
|
def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
local_only = truthy_param?(params["local"])
|
||||||
|
|
||||||
|
activities =
|
||||||
|
params
|
||||||
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|
|> Map.put("local_only", local_only)
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/tag/:tag
|
||||||
|
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
local_only = truthy_param?(params["local"])
|
||||||
|
|
||||||
|
tags =
|
||||||
|
[params["tag"], params["any"]]
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
|
tag_all =
|
||||||
|
params
|
||||||
|
|> Map.get("all", [])
|
||||||
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
|
tag_reject =
|
||||||
|
params
|
||||||
|
|> Map.get("none", [])
|
||||||
|
|> Enum.map(&String.downcase(&1))
|
||||||
|
|
||||||
|
activities =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("local_only", local_only)
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|> Map.put("tag", tags)
|
||||||
|
|> Map.put("tag_all", tag_all)
|
||||||
|
|> Map.put("tag_reject", tag_reject)
|
||||||
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(activities, %{"local" => local_only})
|
||||||
|
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /api/v1/timelines/list/:list_id
|
||||||
|
def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|
||||||
|
with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|
|> Map.put("muting_user", user)
|
||||||
|
|
||||||
|
# we must filter the following list for the user to avoid leaking statuses the user
|
||||||
|
# does not actually have permission to see (for more info, peruse security issue #270).
|
||||||
|
activities =
|
||||||
|
following
|
||||||
|
|> Enum.filter(fn x -> x in user.following end)
|
||||||
|
|> ActivityPub.fetch_activities_bounded(following, params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
render(conn, "index.json", activities: activities, for: user, as: :activity)
|
||||||
|
else
|
||||||
|
_e -> render_error(conn, :forbidden, "Error.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,7 +24,7 @@ def render("participation.json", %{participation: participation, for: user}) do
|
||||||
|
|
||||||
activity = Activity.get_by_id_with_object(last_activity_id)
|
activity = Activity.get_by_id_with_object(last_activity_id)
|
||||||
|
|
||||||
last_status = StatusView.render("status.json", %{activity: activity, for: user})
|
last_status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
# Conversations return all users except the current user.
|
# Conversations return all users except the current user.
|
||||||
users =
|
users =
|
||||||
|
|
|
@ -39,19 +39,19 @@ def render("show.json", %{
|
||||||
"mention" ->
|
"mention" ->
|
||||||
response
|
response
|
||||||
|> Map.merge(%{
|
|> Map.merge(%{
|
||||||
status: StatusView.render("status.json", %{activity: activity, for: user})
|
status: StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
})
|
})
|
||||||
|
|
||||||
"favourite" ->
|
"favourite" ->
|
||||||
response
|
response
|
||||||
|> Map.merge(%{
|
|> Map.merge(%{
|
||||||
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
|
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
|
||||||
})
|
})
|
||||||
|
|
||||||
"reblog" ->
|
"reblog" ->
|
||||||
response
|
response
|
||||||
|> Map.merge(%{
|
|> Map.merge(%{
|
||||||
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
|
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
|
||||||
})
|
})
|
||||||
|
|
||||||
"follow" ->
|
"follow" ->
|
||||||
|
|
|
@ -7,11 +7,10 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
|
||||||
|
|
||||||
alias Pleroma.ScheduledActivity
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
def render("index.json", %{scheduled_activities: scheduled_activities}) do
|
def render("index.json", %{scheduled_activities: scheduled_activities}) do
|
||||||
render_many(scheduled_activities, ScheduledActivityView, "show.json")
|
render_many(scheduled_activities, __MODULE__, "show.json")
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
||||||
|
@ -24,12 +23,8 @@ def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_a
|
||||||
end
|
end
|
||||||
|
|
||||||
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
|
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
|
||||||
try do
|
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
|
||||||
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
|
Map.put(data, :media_attachments, attachments)
|
||||||
Map.put(data, :media_attachments, attachments)
|
|
||||||
rescue
|
|
||||||
_ -> data
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp with_media_attachments(data, _), do: data
|
defp with_media_attachments(data, _), do: data
|
||||||
|
@ -45,13 +40,9 @@ defp status_params(params) do
|
||||||
in_reply_to_id: params["in_reply_to_id"]
|
in_reply_to_id: params["in_reply_to_id"]
|
||||||
}
|
}
|
||||||
|
|
||||||
data =
|
case params["media_ids"] do
|
||||||
if media_ids = params["media_ids"] do
|
nil -> data
|
||||||
Map.put(data, :media_ids, media_ids)
|
media_ids -> Map.put(data, :media_ids, media_ids)
|
||||||
else
|
end
|
||||||
data
|
|
||||||
end
|
|
||||||
|
|
||||||
data
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,17 +73,13 @@ defp reblogged?(activity, user) do
|
||||||
|
|
||||||
def render("index.json", opts) do
|
def render("index.json", opts) do
|
||||||
replied_to_activities = get_replied_to_activities(opts.activities)
|
replied_to_activities = get_replied_to_activities(opts.activities)
|
||||||
|
opts = Map.put(opts, :replied_to_activities, replied_to_activities)
|
||||||
|
|
||||||
opts.activities
|
safe_render_many(opts.activities, StatusView, "show.json", opts)
|
||||||
|> safe_render_many(
|
|
||||||
StatusView,
|
|
||||||
"status.json",
|
|
||||||
Map.put(opts, :replied_to_activities, replied_to_activities)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(
|
def render(
|
||||||
"status.json",
|
"show.json",
|
||||||
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
|
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
|
||||||
) do
|
) do
|
||||||
user = get_user(activity.data["actor"])
|
user = get_user(activity.data["actor"])
|
||||||
|
@ -96,7 +92,7 @@ def render(
|
||||||
|> Activity.with_set_thread_muted_field(opts[:for])
|
|> Activity.with_set_thread_muted_field(opts[:for])
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
reblogged = render("show.json", Map.put(opts, :activity, reblogged_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"] || [])
|
||||||
|
|
||||||
|
@ -144,7 +140,7 @@ def render(
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
user = get_user(activity.data["actor"])
|
user = get_user(activity.data["actor"])
|
||||||
|
@ -303,7 +299,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("status.json", _) do
|
def render("show.json", _) do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -441,6 +437,20 @@ def render("poll.json", %{object: object} = opts) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("context.json", %{activity: activity, activities: activities, user: user}) do
|
||||||
|
%{ancestors: ancestors, descendants: descendants} =
|
||||||
|
activities
|
||||||
|
|> Enum.reverse()
|
||||||
|
|> Enum.group_by(fn %{id: id} -> if id < activity.id, do: :ancestors, else: :descendants end)
|
||||||
|
|> Map.put_new(:ancestors, [])
|
||||||
|
|> Map.put_new(:descendants, [])
|
||||||
|
|
||||||
|
%{
|
||||||
|
ancestors: render("index.json", for: user, activities: ancestors, as: :activity),
|
||||||
|
descendants: render("index.json", for: user, activities: descendants, as: :activity)
|
||||||
|
}
|
||||||
|
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)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.PleromaAPI.PleromaAPI
|
||||||
|
|
||||||
|
def index(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
notifications =
|
||||||
|
user
|
||||||
|
|> PleromaAPI.get_subscription_notifications(params)
|
||||||
|
|> Enum.map(&build_notification_data/1)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(notifications)
|
||||||
|
|> render("index.json", %{notifications: notifications, for: user})
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||||
|
with {:ok, notification} <- SubscriptionNotification.get(user, id) do
|
||||||
|
render(conn, "show.json", %{
|
||||||
|
subscription_notification: build_notification_data(notification),
|
||||||
|
for: user
|
||||||
|
})
|
||||||
|
else
|
||||||
|
{:error, reason} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{"error" => reason})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|
SubscriptionNotification.clear(user)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||||
|
with {:ok, _notif} <- SubscriptionNotification.dismiss(user, id) do
|
||||||
|
json(conn, %{})
|
||||||
|
else
|
||||||
|
{:error, reason} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{"error" => reason})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_multiple(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"ids" => ids} = _params
|
||||||
|
) do
|
||||||
|
SubscriptionNotification.destroy_multiple(user, ids)
|
||||||
|
json(conn, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_notification_data(%{activity: %{data: data}} = notification) do
|
||||||
|
%{
|
||||||
|
notification: notification,
|
||||||
|
actor: User.get_cached_by_ap_id(data["actor"]),
|
||||||
|
parent_activity: Activity.get_create_by_object_ap_id(data["object"])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.PleromaAPI do
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
|
||||||
|
def get_subscription_notifications(user, params \\ %{}) do
|
||||||
|
options = cast_params(params)
|
||||||
|
|
||||||
|
user
|
||||||
|
|> SubscriptionNotification.for_user_query(options)
|
||||||
|
|> restrict(:exclude_types, options)
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cast_params(params) do
|
||||||
|
param_types = %{
|
||||||
|
exclude_types: {:array, :string},
|
||||||
|
reblogs: :boolean,
|
||||||
|
with_muted: :boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
|
changeset.changes
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
|
||||||
|
ap_types =
|
||||||
|
mastodon_types
|
||||||
|
|> Enum.map(&Activity.from_mastodon_notification_type/1)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
|
query
|
||||||
|
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict(query, _, _), do: query
|
||||||
|
end
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
alias Pleroma.Web.PleromaAPI.SubscriptionNotificationView
|
||||||
|
|
||||||
|
def render("index.json", %{notifications: notifications, for: user}) do
|
||||||
|
safe_render_many(notifications, SubscriptionNotificationView, "show.json", %{for: user})
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{
|
||||||
|
subscription_notification: %{
|
||||||
|
notification: %{activity: activity} = notification,
|
||||||
|
actor: actor,
|
||||||
|
parent_activity: parent_activity
|
||||||
|
},
|
||||||
|
for: user
|
||||||
|
}) do
|
||||||
|
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||||
|
|
||||||
|
response = %{
|
||||||
|
id: to_string(notification.id),
|
||||||
|
type: mastodon_type,
|
||||||
|
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
|
||||||
|
account: AccountView.render("account.json", %{user: actor, for: user})
|
||||||
|
}
|
||||||
|
|
||||||
|
case mastodon_type do
|
||||||
|
"mention" ->
|
||||||
|
response
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
})
|
||||||
|
|
||||||
|
"favourite" ->
|
||||||
|
response
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
|
||||||
|
})
|
||||||
|
|
||||||
|
"reblog" ->
|
||||||
|
response
|
||||||
|
|> Map.merge(%{
|
||||||
|
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
|
||||||
|
})
|
||||||
|
|
||||||
|
"follow" ->
|
||||||
|
response
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Metadata.Utils
|
alias Pleroma.Web.Metadata.Utils
|
||||||
alias Pleroma.Web.Push.Subscription
|
alias Pleroma.Web.Push.Subscription
|
||||||
|
@ -19,7 +20,7 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
@types ["Create", "Follow", "Announce", "Like"]
|
@types ["Create", "Follow", "Announce", "Like"]
|
||||||
|
|
||||||
@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() | SubscriptionNotification.t()) :: list(any) | :error
|
||||||
def perform(
|
def perform(
|
||||||
%{
|
%{
|
||||||
activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
|
activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
|
||||||
|
|
|
@ -293,6 +293,14 @@ defmodule Pleroma.Web.Router do
|
||||||
pipe_through(:oauth_read)
|
pipe_through(:oauth_read)
|
||||||
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
|
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
|
||||||
get("/conversations/:id", PleromaAPIController, :conversation)
|
get("/conversations/:id", PleromaAPIController, :conversation)
|
||||||
|
|
||||||
|
scope "/subscription_notifications" do
|
||||||
|
post("/clear", SubscriptionNotificationController, :clear)
|
||||||
|
post("/dismiss", SubscriptionNotificationController, :dismiss)
|
||||||
|
delete("/destroy_multiple", SubscriptionNotificationController, :destroy_multiple)
|
||||||
|
get("/", SubscriptionNotificationController, :index)
|
||||||
|
get("/:id", SubscriptionNotificationController, :show)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
|
@ -315,12 +323,12 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
||||||
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
|
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
|
||||||
|
|
||||||
get("/follow_requests", MastodonAPIController, :follow_requests)
|
get("/follow_requests", FollowRequestController, :index)
|
||||||
get("/blocks", MastodonAPIController, :blocks)
|
get("/blocks", MastodonAPIController, :blocks)
|
||||||
get("/mutes", MastodonAPIController, :mutes)
|
get("/mutes", MastodonAPIController, :mutes)
|
||||||
|
|
||||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
get("/timelines/home", TimelineController, :home)
|
||||||
get("/timelines/direct", MastodonAPIController, :dm_timeline)
|
get("/timelines/direct", TimelineController, :direct)
|
||||||
|
|
||||||
get("/favourites", MastodonAPIController, :favourites)
|
get("/favourites", MastodonAPIController, :favourites)
|
||||||
get("/bookmarks", MastodonAPIController, :bookmarks)
|
get("/bookmarks", MastodonAPIController, :bookmarks)
|
||||||
|
@ -331,16 +339,16 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||||
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
||||||
|
|
||||||
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
|
get("/scheduled_statuses", ScheduledActivityController, :index)
|
||||||
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
|
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
|
||||||
|
|
||||||
get("/lists", ListController, :index)
|
get("/lists", ListController, :index)
|
||||||
get("/lists/:id", ListController, :show)
|
get("/lists/:id", ListController, :show)
|
||||||
get("/lists/:id/accounts", ListController, :list_accounts)
|
get("/lists/:id/accounts", ListController, :list_accounts)
|
||||||
|
|
||||||
get("/domain_blocks", MastodonAPIController, :domain_blocks)
|
get("/domain_blocks", DomainBlockController, :index)
|
||||||
|
|
||||||
get("/filters", MastodonAPIController, :get_filters)
|
get("/filters", FilterController, :index)
|
||||||
|
|
||||||
get("/suggestions", MastodonAPIController, :suggestions)
|
get("/suggestions", MastodonAPIController, :suggestions)
|
||||||
|
|
||||||
|
@ -355,22 +363,22 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
|
||||||
|
|
||||||
post("/statuses", MastodonAPIController, :post_status)
|
post("/statuses", StatusController, :create)
|
||||||
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
delete("/statuses/:id", StatusController, :delete)
|
||||||
|
|
||||||
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
|
post("/statuses/:id/reblog", StatusController, :reblog)
|
||||||
post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
|
post("/statuses/:id/unreblog", StatusController, :unreblog)
|
||||||
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
|
post("/statuses/:id/favourite", StatusController, :favourite)
|
||||||
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
post("/statuses/:id/unfavourite", StatusController, :unfavourite)
|
||||||
post("/statuses/:id/pin", MastodonAPIController, :pin_status)
|
post("/statuses/:id/pin", StatusController, :pin)
|
||||||
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
|
post("/statuses/:id/unpin", StatusController, :unpin)
|
||||||
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
|
post("/statuses/:id/bookmark", StatusController, :bookmark)
|
||||||
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
|
post("/statuses/:id/unbookmark", StatusController, :unbookmark)
|
||||||
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
post("/statuses/:id/mute", StatusController, :mute_conversation)
|
||||||
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
post("/statuses/:id/unmute", StatusController, :unmute_conversation)
|
||||||
|
|
||||||
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
|
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
|
||||||
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
|
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
|
||||||
|
|
||||||
post("/polls/:id/votes", MastodonAPIController, :poll_vote)
|
post("/polls/:id/votes", MastodonAPIController, :poll_vote)
|
||||||
|
|
||||||
|
@ -384,10 +392,10 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/lists/:id/accounts", ListController, :add_to_list)
|
post("/lists/:id/accounts", ListController, :add_to_list)
|
||||||
delete("/lists/:id/accounts", ListController, :remove_from_list)
|
delete("/lists/:id/accounts", ListController, :remove_from_list)
|
||||||
|
|
||||||
post("/filters", MastodonAPIController, :create_filter)
|
post("/filters", FilterController, :create)
|
||||||
get("/filters/:id", MastodonAPIController, :get_filter)
|
get("/filters/:id", FilterController, :show)
|
||||||
put("/filters/:id", MastodonAPIController, :update_filter)
|
put("/filters/:id", FilterController, :update)
|
||||||
delete("/filters/:id", MastodonAPIController, :delete_filter)
|
delete("/filters/:id", FilterController, :delete)
|
||||||
|
|
||||||
patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
|
patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
|
||||||
patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
|
patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
|
||||||
|
@ -411,11 +419,11 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/accounts/:id/mute", MastodonAPIController, :mute)
|
post("/accounts/:id/mute", MastodonAPIController, :mute)
|
||||||
post("/accounts/:id/unmute", MastodonAPIController, :unmute)
|
post("/accounts/:id/unmute", MastodonAPIController, :unmute)
|
||||||
|
|
||||||
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
|
post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
|
||||||
post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
|
post("/follow_requests/:id/reject", FollowRequestController, :reject)
|
||||||
|
|
||||||
post("/domain_blocks", MastodonAPIController, :block_domain)
|
post("/domain_blocks", DomainBlockController, :create)
|
||||||
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
|
delete("/domain_blocks", DomainBlockController, :delete)
|
||||||
|
|
||||||
post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)
|
post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)
|
||||||
post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe)
|
post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe)
|
||||||
|
@ -448,10 +456,10 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
|
get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
|
||||||
get("/custom_emojis", MastodonAPIController, :custom_emojis)
|
get("/custom_emojis", MastodonAPIController, :custom_emojis)
|
||||||
|
|
||||||
get("/statuses/:id/card", MastodonAPIController, :status_card)
|
get("/statuses/:id/card", StatusController, :card)
|
||||||
|
|
||||||
get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
|
get("/statuses/:id/favourited_by", StatusController, :favourited_by)
|
||||||
get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
|
get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)
|
||||||
|
|
||||||
get("/trends", MastodonAPIController, :empty_array)
|
get("/trends", MastodonAPIController, :empty_array)
|
||||||
|
|
||||||
|
@ -466,13 +474,13 @@ defmodule Pleroma.Web.Router do
|
||||||
scope [] do
|
scope [] do
|
||||||
pipe_through(:oauth_read_or_public)
|
pipe_through(:oauth_read_or_public)
|
||||||
|
|
||||||
get("/timelines/public", MastodonAPIController, :public_timeline)
|
get("/timelines/public", TimelineController, :public)
|
||||||
get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
|
get("/timelines/tag/:tag", TimelineController, :hashtag)
|
||||||
get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
|
get("/timelines/list/:list_id", TimelineController, :list)
|
||||||
|
|
||||||
get("/statuses", MastodonAPIController, :get_statuses)
|
get("/statuses", StatusController, :index)
|
||||||
get("/statuses/:id", MastodonAPIController, :get_status)
|
get("/statuses/:id", StatusController, :show)
|
||||||
get("/statuses/:id/context", MastodonAPIController, :get_context)
|
get("/statuses/:id/context", StatusController, :context)
|
||||||
|
|
||||||
get("/polls/:id", MastodonAPIController, :get_poll)
|
get("/polls/:id", MastodonAPIController, :get_poll)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ def render("update.json", %Activity{} = activity, %User{} = user) do
|
||||||
event: "update",
|
event: "update",
|
||||||
payload:
|
payload:
|
||||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||||
"status.json",
|
"show.json",
|
||||||
activity: activity,
|
activity: activity,
|
||||||
for: user
|
for: user
|
||||||
)
|
)
|
||||||
|
@ -43,7 +43,7 @@ def render("update.json", %Activity{} = activity) do
|
||||||
event: "update",
|
event: "update",
|
||||||
payload:
|
payload:
|
||||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||||
"status.json",
|
"show.json",
|
||||||
activity: activity
|
activity: activity
|
||||||
)
|
)
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
|
|
3
mix.exs
3
mix.exs
|
@ -159,6 +159,9 @@ defp deps do
|
||||||
{:plug_static_index_html, "~> 1.0.0"},
|
{:plug_static_index_html, "~> 1.0.0"},
|
||||||
{:excoveralls, "~> 0.11.1", only: :test},
|
{:excoveralls, "~> 0.11.1", only: :test},
|
||||||
{:flake_id, "~> 0.1.0"},
|
{:flake_id, "~> 0.1.0"},
|
||||||
|
{:remote_ip,
|
||||||
|
git: "https://git.pleroma.social/pleroma/remote_ip.git",
|
||||||
|
ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"},
|
||||||
{:mox, "~> 0.5", only: :test}
|
{:mox, "~> 0.5", only: :test}
|
||||||
] ++ oauth_deps()
|
] ++ oauth_deps()
|
||||||
end
|
end
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -48,6 +48,7 @@
|
||||||
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
|
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
|
||||||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm"},
|
||||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
|
"joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
@ -87,6 +88,7 @@
|
||||||
"quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},
|
"quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||||
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
||||||
|
"remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||||
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"},
|
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
|
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateSubscriptionNotifications do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create_if_not_exists table(:subscription_notifications) 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_if_not_exists(index(:subscription_notifications, [:user_id]))
|
||||||
|
create_if_not_exists(index(:subscription_notifications, ["id desc nulls last"]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -68,7 +68,7 @@ test "receives well formatted events" do
|
||||||
assert {:ok, json} = Jason.decode(json["payload"])
|
assert {:ok, json} = Jason.decode(json["payload"])
|
||||||
|
|
||||||
view_json =
|
view_json =
|
||||||
Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: activity, for: nil)
|
Pleroma.Web.MastodonAPI.StatusView.render("show.json", activity: activity, for: nil)
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
|> Jason.decode!()
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
|
|
@ -33,16 +33,16 @@ test "notifies someone when they are directly addressed" do
|
||||||
assert other_notification.activity_id == activity.id
|
assert other_notification.activity_id == activity.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it creates a notification for subscribed users" do
|
test "it does not create a notification for subscribed users" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
subscriber = insert(:user)
|
subscriber = insert(:user)
|
||||||
|
|
||||||
User.subscribe(subscriber, user)
|
User.subscribe(subscriber, user)
|
||||||
|
|
||||||
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
|
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
|
||||||
{:ok, [notification]} = Notification.create_notifications(status)
|
{:ok, notifications} = Notification.create_notifications(status)
|
||||||
|
|
||||||
assert notification.user_id == subscriber.id
|
assert notifications == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not create a notification for subscribed users if status is a reply" do
|
test "does not create a notification for subscribed users if status is a reply" do
|
||||||
|
@ -182,14 +182,16 @@ test "it doesn't create a notification for follow-unfollow-follow chains" do
|
||||||
refute Notification.create_notification(activity_dupe, followed_user)
|
refute Notification.create_notification(activity_dupe, followed_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't create duplicate notifications for follow+subscribed users" do
|
test "it doesn't create notifications for follow+subscribed users" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
subscriber = insert(:user)
|
subscriber = insert(:user)
|
||||||
|
|
||||||
{:ok, _, _, _} = CommonAPI.follow(subscriber, user)
|
{:ok, _, _, _} = CommonAPI.follow(subscriber, user)
|
||||||
User.subscribe(subscriber, user)
|
User.subscribe(subscriber, user)
|
||||||
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
|
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
|
||||||
{:ok, [_notif]} = Notification.create_notifications(status)
|
{:ok, notifications} = Notification.create_notifications(status)
|
||||||
|
|
||||||
|
assert notifications == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it doesn't create subscription notifications if the recipient cannot see the status" do
|
test "it doesn't create subscription notifications if the recipient cannot see the status" do
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Plugs.RemoteIpTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
use Plug.Test
|
||||||
|
|
||||||
|
alias Pleroma.Plugs.RemoteIp
|
||||||
|
|
||||||
|
test "disabled" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: false)
|
||||||
|
|
||||||
|
%{remote_ip: remote_ip} = conn(:get, "/")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == remote_ip
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enabled" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: true)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "custom headers" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: true, headers: ["cf-connecting-ip"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
refute conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("cf-connecting-ip", "1.1.1.1")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "custom proxies" do
|
||||||
|
Pleroma.Config.put(RemoteIp, enabled: true)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
refute conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
|
||||||
|
Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.0/20"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn(:get, "/")
|
||||||
|
|> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
|
||||||
|
|> RemoteIp.call(nil)
|
||||||
|
|
||||||
|
assert conn.remote_ip == {1, 1, 1, 1}
|
||||||
|
end
|
||||||
|
end
|
|
@ -61,7 +61,7 @@ test "includes reported statuses" do
|
||||||
AccountView.render("account.json", %{user: other_user}),
|
AccountView.render("account.json", %{user: other_user}),
|
||||||
Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})
|
Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})
|
||||||
),
|
),
|
||||||
statuses: [StatusView.render("status.json", %{activity: activity})],
|
statuses: [StatusView.render("show.json", %{activity: activity})],
|
||||||
state: "open",
|
state: "open",
|
||||||
id: report_activity.id
|
id: report_activity.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "blocking / unblocking a domain", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
|
assert User.blocks?(user, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
|
refute User.blocks?(user, other_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting a list of domain blocks", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user} = User.block_domain(user, "bad.site")
|
||||||
|
{:ok, user} = User.block_domain(user, "even.worse.site")
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/domain_blocks")
|
||||||
|
|
||||||
|
domain_blocks = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert "bad.site" in domain_blocks
|
||||||
|
assert "even.worse.site" in domain_blocks
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,137 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Web.MastodonAPI.FilterView
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "creating a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
filter = %Pleroma.Filter{
|
||||||
|
phrase: "knights",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
|
||||||
|
|
||||||
|
assert response = json_response(conn, 200)
|
||||||
|
assert response["phrase"] == filter.phrase
|
||||||
|
assert response["context"] == filter.context
|
||||||
|
assert response["irreversible"] == false
|
||||||
|
assert response["id"] != nil
|
||||||
|
assert response["id"] != ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fetching a list of filters", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query_one = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 1,
|
||||||
|
phrase: "knights",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
query_two = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "who",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, filter_one} = Pleroma.Filter.create(query_one)
|
||||||
|
{:ok, filter_two} = Pleroma.Filter.create(query_two)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/filters")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert response ==
|
||||||
|
render_json(
|
||||||
|
FilterView,
|
||||||
|
"filters.json",
|
||||||
|
filters: [filter_two, filter_one]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "knight",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, filter} = Pleroma.Filter.create(query)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/filters/#{filter.filter_id}")
|
||||||
|
|
||||||
|
assert _response = json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "knight",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, _filter} = Pleroma.Filter.create(query)
|
||||||
|
|
||||||
|
new = %Pleroma.Filter{
|
||||||
|
phrase: "nii",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/filters/#{query.filter_id}", %{
|
||||||
|
phrase: new.phrase,
|
||||||
|
context: new.context
|
||||||
|
})
|
||||||
|
|
||||||
|
assert response = json_response(conn, 200)
|
||||||
|
assert response["phrase"] == new.phrase
|
||||||
|
assert response["context"] == new.context
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete a filter", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
query = %Pleroma.Filter{
|
||||||
|
user_id: user.id,
|
||||||
|
filter_id: 2,
|
||||||
|
phrase: "knight",
|
||||||
|
context: ["home"]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, filter} = Pleroma.Filter.create(query)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/filters/#{filter.filter_id}")
|
||||||
|
|
||||||
|
assert response = json_response(conn, 200)
|
||||||
|
assert response == %{}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "locked accounts" do
|
||||||
|
test "/api/v1/follow_requests works" do
|
||||||
|
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/follow_requests")
|
||||||
|
|
||||||
|
assert [relationship] = json_response(conn, 200)
|
||||||
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/api/v1/follow_requests/:id/authorize works" do
|
||||||
|
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/follow_requests/#{other_user.id}/authorize")
|
||||||
|
|
||||||
|
assert relationship = json_response(conn, 200)
|
||||||
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "/api/v1/follow_requests/:id/reject works" do
|
||||||
|
user = insert(:user, %{info: %User.Info{locked: true}})
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/follow_requests/#{other_user.id}/reject")
|
||||||
|
|
||||||
|
assert relationship = json_response(conn, 200)
|
||||||
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
|
||||||
|
user = User.get_cached_by_id(user.id)
|
||||||
|
other_user = User.get_cached_by_id(other_user.id)
|
||||||
|
|
||||||
|
assert User.following?(other_user, user) == false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "shows scheduled activities", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
||||||
|
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/404")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
new_scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||||
|
scheduled_at: new_scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response(res_conn, 200)
|
||||||
|
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,291 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Tesla.Mock
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.OStatus
|
||||||
|
|
||||||
|
clear_config([:instance, :public])
|
||||||
|
|
||||||
|
setup do
|
||||||
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the home timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
following = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/home")
|
||||||
|
|
||||||
|
assert Enum.empty?(json_response(conn, :ok))
|
||||||
|
|
||||||
|
{:ok, user} = User.follow(user, following)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/home")
|
||||||
|
|
||||||
|
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "public" do
|
||||||
|
@tag capture_log: true
|
||||||
|
test "the public timeline", %{conn: conn} do
|
||||||
|
following = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
|
||||||
|
|
||||||
|
{:ok, [_activity]} =
|
||||||
|
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"})
|
||||||
|
|
||||||
|
assert length(json_response(conn, :ok)) == 2
|
||||||
|
|
||||||
|
conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"})
|
||||||
|
|
||||||
|
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
||||||
|
|
||||||
|
conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"})
|
||||||
|
|
||||||
|
assert [%{"content" => "test"}] = json_response(conn, :ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the public timeline when public is set to false", %{conn: conn} do
|
||||||
|
Config.put([:instance, :public], false)
|
||||||
|
|
||||||
|
assert %{"error" => "This resource requires authentication."} ==
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/timelines/public", %{"local" => "False"})
|
||||||
|
|> json_response(:forbidden)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the public timeline includes only public statuses for an authenticated user" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
|
||||||
|
|
||||||
|
res_conn = get(conn, "/api/v1/timelines/public")
|
||||||
|
assert length(json_response(res_conn, 200)) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "direct" do
|
||||||
|
test "direct timeline", %{conn: conn} do
|
||||||
|
user_one = insert(:user)
|
||||||
|
user_two = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user_two} = User.follow(user_two, user_one)
|
||||||
|
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _follower_only} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Only direct should be visible here
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
assert %{"visibility" => "direct"} = status
|
||||||
|
assert status["url"] != direct.data["id"]
|
||||||
|
|
||||||
|
# User should be able to see their own direct message
|
||||||
|
res_conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user_one)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
assert %{"visibility" => "direct"} = status
|
||||||
|
|
||||||
|
# Both should be visible here
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/home")
|
||||||
|
|
||||||
|
[_s1, _s2] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
# Test pagination
|
||||||
|
Enum.each(1..20, fn _ ->
|
||||||
|
{:ok, _} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
statuses = json_response(res_conn, :ok)
|
||||||
|
assert length(statuses) == 20
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
|
||||||
|
assert status["url"] != direct.data["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't include DMs from blocked users", %{conn: conn} do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
|
|
||||||
|
{:ok, _blocked_direct} =
|
||||||
|
CommonAPI.post(blocked, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "Hi @#{blocker.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, :ok)
|
||||||
|
assert status["id"] == direct.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "list" do
|
||||||
|
test "list timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
|
||||||
|
{:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/list/#{list.id}")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(conn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity_two.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
|
||||||
|
|
||||||
|
{:ok, _activity_two} =
|
||||||
|
CommonAPI.post(other_user, %{
|
||||||
|
"status" => "Marisa is cute.",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, list} = Pleroma.List.create("name", user)
|
||||||
|
{:ok, list} = Pleroma.List.follow(list, other_user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/timelines/list/#{list.id}")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(conn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity_one.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "hashtag" do
|
||||||
|
@tag capture_log: true
|
||||||
|
test "hashtag timeline", %{conn: conn} do
|
||||||
|
following = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
|
||||||
|
|
||||||
|
{:ok, [_activity]} =
|
||||||
|
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
|
||||||
|
|
||||||
|
nconn = get(conn, "/api/v1/timelines/tag/2hu")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(nconn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity.id)
|
||||||
|
|
||||||
|
# works for different capitalization too
|
||||||
|
nconn = get(conn, "/api/v1/timelines/tag/2HU")
|
||||||
|
|
||||||
|
assert [%{"id" => id}] = json_response(nconn, :ok)
|
||||||
|
|
||||||
|
assert id == to_string(activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "multi-hashtag timeline", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
|
||||||
|
{:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
|
||||||
|
{:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
|
||||||
|
|
||||||
|
any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]})
|
||||||
|
|
||||||
|
[status_none, status_test1, status_test] = json_response(any_test, :ok)
|
||||||
|
|
||||||
|
assert to_string(activity_test.id) == status_test["id"]
|
||||||
|
assert to_string(activity_test1.id) == status_test1["id"]
|
||||||
|
assert to_string(activity_none.id) == status_none["id"]
|
||||||
|
|
||||||
|
restricted_test =
|
||||||
|
get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
|
||||||
|
|
||||||
|
assert [status_test1] == json_response(restricted_test, :ok)
|
||||||
|
|
||||||
|
all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]})
|
||||||
|
|
||||||
|
assert [status_none] == json_response(all_test, :ok)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
|
@ -75,9 +75,9 @@ test "returns notifications for user" do
|
||||||
|
|
||||||
User.subscribe(subscriber, user)
|
User.subscribe(subscriber, user)
|
||||||
|
|
||||||
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
|
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin @#{subscriber.nickname}"})
|
||||||
|
|
||||||
{:ok, status1} = CommonAPI.post(user, %{"status" => "Magi"})
|
{:ok, status1} = CommonAPI.post(user, %{"status" => "Magi @#{subscriber.nickname}"})
|
||||||
{:ok, [notification]} = Notification.create_notifications(status)
|
{:ok, [notification]} = Notification.create_notifications(status)
|
||||||
{:ok, [notification1]} = Notification.create_notifications(status1)
|
{:ok, [notification1]} = Notification.create_notifications(status1)
|
||||||
res = MastodonAPI.get_notifications(subscriber)
|
res = MastodonAPI.get_notifications(subscriber)
|
||||||
|
|
|
@ -28,7 +28,7 @@ test "Mention notification" do
|
||||||
pleroma: %{is_seen: false},
|
pleroma: %{is_seen: false},
|
||||||
type: "mention",
|
type: "mention",
|
||||||
account: AccountView.render("account.json", %{user: user, for: mentioned_user}),
|
account: AccountView.render("account.json", %{user: user, for: mentioned_user}),
|
||||||
status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}),
|
status: StatusView.render("show.json", %{activity: activity, for: mentioned_user}),
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ test "Favourite notification" do
|
||||||
pleroma: %{is_seen: false},
|
pleroma: %{is_seen: false},
|
||||||
type: "favourite",
|
type: "favourite",
|
||||||
account: AccountView.render("account.json", %{user: another_user, for: user}),
|
account: AccountView.render("account.json", %{user: another_user, for: user}),
|
||||||
status: StatusView.render("status.json", %{activity: create_activity, for: user}),
|
status: StatusView.render("show.json", %{activity: create_activity, for: user}),
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ test "Reblog notification" do
|
||||||
pleroma: %{is_seen: false},
|
pleroma: %{is_seen: false},
|
||||||
type: "reblog",
|
type: "reblog",
|
||||||
account: AccountView.render("account.json", %{user: another_user, for: user}),
|
account: AccountView.render("account.json", %{user: another_user, for: user}),
|
||||||
status: StatusView.render("status.json", %{activity: reblog_activity, for: user}),
|
status: StatusView.render("show.json", %{activity: reblog_activity, for: user}),
|
||||||
created_at: Utils.to_masto_date(notification.inserted_at)
|
created_at: Utils.to_masto_date(notification.inserted_at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ test "returns the direct conversation id when given the `with_conversation_id` o
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
|
||||||
|
|
||||||
status =
|
status =
|
||||||
StatusView.render("status.json",
|
StatusView.render("show.json",
|
||||||
activity: activity,
|
activity: activity,
|
||||||
with_direct_conversation_id: true,
|
with_direct_conversation_id: true,
|
||||||
for: user
|
for: user
|
||||||
|
@ -46,7 +46,7 @@ test "returns a temporary ap_id based user for activities missing db users" do
|
||||||
Repo.delete(user)
|
Repo.delete(user)
|
||||||
Cachex.clear(:user_cache)
|
Cachex.clear(:user_cache)
|
||||||
|
|
||||||
%{account: ms_user} = StatusView.render("status.json", activity: activity)
|
%{account: ms_user} = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert ms_user.acct == "erroruser@example.com"
|
assert ms_user.acct == "erroruser@example.com"
|
||||||
end
|
end
|
||||||
|
@ -63,7 +63,7 @@ test "tries to get a user by nickname if fetching by ap_id doesn't work" do
|
||||||
|
|
||||||
Cachex.clear(:user_cache)
|
Cachex.clear(:user_cache)
|
||||||
|
|
||||||
result = StatusView.render("status.json", activity: activity)
|
result = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert result[:account][:id] == to_string(user.id)
|
assert result[:account][:id] == to_string(user.id)
|
||||||
end
|
end
|
||||||
|
@ -81,7 +81,7 @@ test "a note with null content" do
|
||||||
|
|
||||||
User.get_cached_by_ap_id(note.data["actor"])
|
User.get_cached_by_ap_id(note.data["actor"])
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: note})
|
status = StatusView.render("show.json", %{activity: note})
|
||||||
|
|
||||||
assert status.content == ""
|
assert status.content == ""
|
||||||
end
|
end
|
||||||
|
@ -93,7 +93,7 @@ test "a note activity" do
|
||||||
|
|
||||||
convo_id = Utils.context_to_conversation_id(object_data["context"])
|
convo_id = Utils.context_to_conversation_id(object_data["context"])
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: note})
|
status = StatusView.render("show.json", %{activity: note})
|
||||||
|
|
||||||
created_at =
|
created_at =
|
||||||
(object_data["published"] || "")
|
(object_data["published"] || "")
|
||||||
|
@ -165,11 +165,11 @@ test "tells if the message is muted for some reason" do
|
||||||
{:ok, user} = User.mute(user, other_user)
|
{:ok, user} = User.mute(user, other_user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
||||||
status = StatusView.render("status.json", %{activity: activity})
|
status = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert status.muted == false
|
assert status.muted == false
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity, for: user})
|
status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert status.muted == true
|
assert status.muted == true
|
||||||
end
|
end
|
||||||
|
@ -181,13 +181,13 @@ test "tells if the message is thread muted" do
|
||||||
{:ok, user} = User.mute(user, other_user)
|
{:ok, user} = User.mute(user, other_user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
|
||||||
status = StatusView.render("status.json", %{activity: activity, for: user})
|
status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert status.pleroma.thread_muted == false
|
assert status.pleroma.thread_muted == false
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.add_mute(user, activity)
|
{:ok, activity} = CommonAPI.add_mute(user, activity)
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity, for: user})
|
status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert status.pleroma.thread_muted == true
|
assert status.pleroma.thread_muted == true
|
||||||
end
|
end
|
||||||
|
@ -196,11 +196,11 @@ test "tells if the status is bookmarked" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"})
|
||||||
status = StatusView.render("status.json", %{activity: activity})
|
status = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert status.bookmarked == false
|
assert status.bookmarked == false
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity, for: user})
|
status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert status.bookmarked == false
|
assert status.bookmarked == false
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ test "tells if the status is bookmarked" do
|
||||||
|
|
||||||
activity = Activity.get_by_id_with_object(activity.id)
|
activity = Activity.get_by_id_with_object(activity.id)
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity, for: user})
|
status = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert status.bookmarked == true
|
assert status.bookmarked == true
|
||||||
end
|
end
|
||||||
|
@ -220,7 +220,7 @@ test "a reply" do
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id})
|
CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id})
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity})
|
status = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert status.in_reply_to_id == to_string(note.id)
|
assert status.in_reply_to_id == to_string(note.id)
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ test "contains mentions" do
|
||||||
|
|
||||||
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
{:ok, [activity]} = OStatus.handle_incoming(incoming)
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity})
|
status = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert status.mentions ==
|
assert status.mentions ==
|
||||||
Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end)
|
Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end)
|
||||||
|
@ -263,7 +263,7 @@ test "create mentions from the 'to' field" do
|
||||||
|
|
||||||
assert length(activity.recipients) == 3
|
assert length(activity.recipients) == 3
|
||||||
|
|
||||||
%{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity})
|
%{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert length(mentions) == 1
|
assert length(mentions) == 1
|
||||||
assert mention.url == recipient_ap_id
|
assert mention.url == recipient_ap_id
|
||||||
|
@ -300,7 +300,7 @@ test "create mentions from the 'tag' field" do
|
||||||
|
|
||||||
assert length(activity.recipients) == 3
|
assert length(activity.recipients) == 3
|
||||||
|
|
||||||
%{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity})
|
%{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert length(mentions) == 1
|
assert length(mentions) == 1
|
||||||
assert mention.url == recipient.ap_id
|
assert mention.url == recipient.ap_id
|
||||||
|
@ -340,7 +340,7 @@ test "put the url advertised in the Activity in to the url attribute" do
|
||||||
id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
|
id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"
|
||||||
[activity] = Activity.search(nil, id)
|
[activity] = Activity.search(nil, id)
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity})
|
status = StatusView.render("show.json", %{activity: activity})
|
||||||
|
|
||||||
assert status.uri == id
|
assert status.uri == id
|
||||||
assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
|
assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
|
||||||
|
@ -352,7 +352,7 @@ test "a reblog" do
|
||||||
|
|
||||||
{:ok, reblog, _} = CommonAPI.repeat(activity.id, user)
|
{:ok, reblog, _} = CommonAPI.repeat(activity.id, user)
|
||||||
|
|
||||||
represented = StatusView.render("status.json", %{for: user, activity: reblog})
|
represented = StatusView.render("show.json", %{for: user, activity: reblog})
|
||||||
|
|
||||||
assert represented[:id] == to_string(reblog.id)
|
assert represented[:id] == to_string(reblog.id)
|
||||||
assert represented[:reblog][:id] == to_string(activity.id)
|
assert represented[:reblog][:id] == to_string(activity.id)
|
||||||
|
@ -369,7 +369,7 @@ test "a peertube video" do
|
||||||
|
|
||||||
%Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
|
%Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
|
||||||
|
|
||||||
represented = StatusView.render("status.json", %{for: user, activity: activity})
|
represented = StatusView.render("show.json", %{for: user, activity: activity})
|
||||||
|
|
||||||
assert represented[:id] == to_string(activity.id)
|
assert represented[:id] == to_string(activity.id)
|
||||||
assert length(represented[:media_attachments]) == 1
|
assert length(represented[:media_attachments]) == 1
|
||||||
|
@ -570,7 +570,7 @@ test "embeds a relationship in the account" do
|
||||||
"status" => "drink more water"
|
"status" => "drink more water"
|
||||||
})
|
})
|
||||||
|
|
||||||
result = StatusView.render("status.json", %{activity: activity, for: other_user})
|
result = StatusView.render("show.json", %{activity: activity, for: other_user})
|
||||||
|
|
||||||
assert result[:account][:pleroma][:relationship] ==
|
assert result[:account][:pleroma][:relationship] ==
|
||||||
AccountView.render("relationship.json", %{user: other_user, target: user})
|
AccountView.render("relationship.json", %{user: other_user, target: user})
|
||||||
|
@ -587,7 +587,7 @@ test "embeds a relationship in the account in reposts" do
|
||||||
|
|
||||||
{:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user)
|
{:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user)
|
||||||
|
|
||||||
result = StatusView.render("status.json", %{activity: activity, for: user})
|
result = StatusView.render("show.json", %{activity: activity, for: user})
|
||||||
|
|
||||||
assert result[:account][:pleroma][:relationship] ==
|
assert result[:account][:pleroma][:relationship] ==
|
||||||
AccountView.render("relationship.json", %{user: user, target: other_user})
|
AccountView.render("relationship.json", %{user: user, target: other_user})
|
||||||
|
@ -604,7 +604,7 @@ test "visibility/list" do
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
|
CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
|
||||||
|
|
||||||
status = StatusView.render("status.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert status.visibility == "list"
|
assert status.visibility == "list"
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.SubscriptionNotification
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Tesla.Mock
|
||||||
|
|
||||||
|
setup do
|
||||||
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
clear_config([:instance, :public])
|
||||||
|
clear_config([:rich_media, :enabled])
|
||||||
|
|
||||||
|
describe "subscription_notifications" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
subscriber = insert(:user)
|
||||||
|
|
||||||
|
User.subscribe(subscriber, user)
|
||||||
|
|
||||||
|
{:ok, %{user: user, subscriber: subscriber}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list of notifications", %{conn: conn, user: user, subscriber: subscriber} do
|
||||||
|
status_text = "Hello"
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => status_text})
|
||||||
|
path = subscription_notification_path(conn, :index)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> get(path)
|
||||||
|
|
||||||
|
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
|
||||||
|
assert response == status_text
|
||||||
|
end
|
||||||
|
|
||||||
|
test "getting a single notification", %{conn: conn, user: user, subscriber: subscriber} do
|
||||||
|
status_text = "Hello"
|
||||||
|
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => status_text})
|
||||||
|
[notification] = Repo.all(SubscriptionNotification)
|
||||||
|
|
||||||
|
path = subscription_notification_path(conn, :show, notification)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> get(path)
|
||||||
|
|
||||||
|
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
|
||||||
|
assert response == status_text
|
||||||
|
end
|
||||||
|
|
||||||
|
test "dismissing a single notification also deletes it", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
subscriber: subscriber
|
||||||
|
} do
|
||||||
|
status_text = "Hello"
|
||||||
|
{:ok, _activity} = CommonAPI.post(user, %{"status" => status_text})
|
||||||
|
|
||||||
|
[notification] = Repo.all(SubscriptionNotification)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> post(subscription_notification_path(conn, :dismiss), %{"id" => notification.id})
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert Repo.all(SubscriptionNotification) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "clearing all notifications also deletes them", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
subscriber: subscriber
|
||||||
|
} do
|
||||||
|
status_text1 = "Hello"
|
||||||
|
status_text2 = "Hello again"
|
||||||
|
{:ok, _activity1} = CommonAPI.post(user, %{"status" => status_text1})
|
||||||
|
{:ok, _activity2} = CommonAPI.post(user, %{"status" => status_text2})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> post(subscription_notification_path(conn, :clear))
|
||||||
|
|
||||||
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, subscriber)
|
||||||
|
|> get(subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == []
|
||||||
|
|
||||||
|
assert Repo.all(SubscriptionNotification) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates notifications using min_id, since_id, max_id, and limit", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user,
|
||||||
|
subscriber: subscriber
|
||||||
|
} do
|
||||||
|
{:ok, activity1} = CommonAPI.post(user, %{"status" => "Hello 1"})
|
||||||
|
{:ok, activity2} = CommonAPI.post(user, %{"status" => "Hello 2"})
|
||||||
|
{:ok, activity3} = CommonAPI.post(user, %{"status" => "Hello 3"})
|
||||||
|
{:ok, activity4} = CommonAPI.post(user, %{"status" => "Hello 4"})
|
||||||
|
|
||||||
|
notification1_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity1.id).id |> to_string()
|
||||||
|
|
||||||
|
notification2_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity2.id).id |> to_string()
|
||||||
|
|
||||||
|
notification3_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity3.id).id |> to_string()
|
||||||
|
|
||||||
|
notification4_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity4.id).id |> to_string()
|
||||||
|
|
||||||
|
conn = assign(conn, :user, subscriber)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
subscription_notification_path(conn, :index, %{
|
||||||
|
"limit" => 2,
|
||||||
|
"min_id" => notification1_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
subscription_notification_path(conn, :index, %{
|
||||||
|
"limit" => 2,
|
||||||
|
"since_id" => notification1_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
subscription_notification_path(conn, :index, %{
|
||||||
|
"limit" => 2,
|
||||||
|
"max_id" => notification4_id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "destroy multiple", %{conn: conn, user: user1, subscriber: user2} do
|
||||||
|
# mutual subscription
|
||||||
|
User.subscribe(user1, user2)
|
||||||
|
|
||||||
|
{:ok, activity1} = CommonAPI.post(user1, %{"status" => "Hello 1"})
|
||||||
|
{:ok, activity2} = CommonAPI.post(user1, %{"status" => "World 1"})
|
||||||
|
{:ok, activity3} = CommonAPI.post(user2, %{"status" => "Hello 2"})
|
||||||
|
{:ok, activity4} = CommonAPI.post(user2, %{"status" => "World 2"})
|
||||||
|
|
||||||
|
notification1_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity1.id).id |> to_string()
|
||||||
|
|
||||||
|
notification2_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity2.id).id |> to_string()
|
||||||
|
|
||||||
|
notification3_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity3.id).id |> to_string()
|
||||||
|
|
||||||
|
notification4_id =
|
||||||
|
Repo.get_by(SubscriptionNotification, activity_id: activity4.id).id |> to_string()
|
||||||
|
|
||||||
|
conn = assign(conn, :user, user1)
|
||||||
|
|
||||||
|
conn_res = get(conn, subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
|
||||||
|
Enum.each(result, fn %{"id" => id} ->
|
||||||
|
assert id in [notification3_id, notification4_id]
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn2 = assign(conn, :user, user2)
|
||||||
|
|
||||||
|
conn_res = get(conn2, subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
|
||||||
|
Enum.each(result, fn %{"id" => id} ->
|
||||||
|
assert id in [notification1_id, notification2_id]
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn_destroy =
|
||||||
|
delete(conn, subscription_notification_path(conn, :destroy_multiple), %{
|
||||||
|
"ids" => [notification3_id, notification4_id]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert json_response(conn_destroy, 200) == %{}
|
||||||
|
|
||||||
|
conn_res = get(conn2, subscription_notification_path(conn, :index))
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
|
||||||
|
Enum.each(result, fn %{"id" => id} ->
|
||||||
|
assert id in [notification1_id, notification2_id]
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert length(Repo.all(SubscriptionNotification)) == 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue