Merge branch 'main' into move_views_to_diesel

This commit is contained in:
Dessalines 2020-12-08 13:17:55 -05:00
commit 46e38bf714
67 changed files with 709 additions and 657 deletions

94
Cargo.lock generated
View File

@ -8,7 +8,7 @@ checksum = "5e9fedbe571e267d9b93d071bdc4493f944022c6cce717ebb27d352026fc81c4"
dependencies = [
"chrono",
"mime",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"thiserror",
"url",
@ -21,7 +21,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb8e19a0810cc25df3535061a08b7d8f8a734d309ea4411c57a9767e4a2ffa0e"
dependencies = [
"activitystreams",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
]
@ -148,7 +148,7 @@ dependencies = [
"pin-project 1.0.2",
"rand",
"regex",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"serde_urlencoded",
"sha-1 0.9.2",
@ -176,7 +176,7 @@ dependencies = [
"http",
"log",
"regex",
"serde 1.0.117",
"serde 1.0.118",
]
[[package]]
@ -320,7 +320,7 @@ dependencies = [
"pin-project 1.0.2",
"regex",
"rustls",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"serde_urlencoded",
"socket2",
@ -399,9 +399,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.34"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
[[package]]
name = "arrayvec"
@ -466,7 +466,7 @@ dependencies = [
"percent-encoding",
"rand",
"rustls",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"serde_urlencoded",
]
@ -496,7 +496,7 @@ dependencies = [
"log",
"num_cpus",
"rand",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"thiserror",
"tokio 0.2.23",
@ -515,7 +515,7 @@ dependencies = [
"async-trait",
"chrono",
"log",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"thiserror",
"tokio 0.2.23",
@ -745,7 +745,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits 0.2.14",
"serde 1.0.117",
"serde 1.0.118",
"time 0.1.44",
"winapi 0.3.9",
]
@ -759,15 +759,6 @@ dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
"bitflags",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -800,7 +791,7 @@ checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [
"lazy_static",
"nom 5.1.2",
"serde 1.0.117",
"serde 1.0.118",
"serde-hjson",
]
@ -1686,7 +1677,7 @@ dependencies = [
"base64 0.12.3",
"pem",
"ring",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"simple_asn1",
]
@ -1745,7 +1736,7 @@ dependencies = [
"openssl",
"rand",
"reqwest",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"sha2",
"strum",
@ -1789,7 +1780,7 @@ dependencies = [
"percent-encoding",
"rand",
"reqwest",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"sha2",
"strum",
@ -1811,7 +1802,7 @@ dependencies = [
"lemmy_utils",
"log",
"regex",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"sha2",
"strum",
@ -1862,7 +1853,7 @@ dependencies = [
"openssl",
"reqwest",
"rss",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"sha2",
"strum",
@ -1880,7 +1871,7 @@ dependencies = [
"lemmy_db",
"lemmy_utils",
"log",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
]
@ -1903,7 +1894,7 @@ dependencies = [
"rand",
"regex",
"reqwest",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"thiserror",
"url",
@ -1925,7 +1916,7 @@ dependencies = [
"log",
"rand",
"reqwest",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"strum",
"strum_macros",
@ -1950,7 +1941,7 @@ dependencies = [
"r2d2",
"rand",
"regex",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"uuid",
]
@ -1970,9 +1961,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "linked-hash-map"
@ -2367,12 +2358,11 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.8.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0"
dependencies = [
"cfg-if 0.1.10",
"cloudabi",
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
@ -2723,7 +2713,7 @@ dependencies = [
"native-tls",
"percent-encoding",
"pin-project-lite 0.2.0",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"serde_urlencoded",
"tokio 0.2.23",
@ -2907,9 +2897,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]]
name = "serde"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
@ -2929,9 +2919,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
@ -2947,7 +2937,7 @@ dependencies = [
"indexmap",
"itoa",
"ryu",
"serde 1.0.117",
"serde 1.0.118",
]
[[package]]
@ -2968,7 +2958,7 @@ dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde 1.0.117",
"serde 1.0.118",
]
[[package]]
@ -3049,9 +3039,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "socket2"
@ -3108,7 +3098,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote",
"serde 1.0.117",
"serde 1.0.118",
"serde_derive",
"syn",
]
@ -3122,7 +3112,7 @@ dependencies = [
"base-x",
"proc-macro2",
"quote",
"serde 1.0.117",
"serde 1.0.118",
"serde_derive",
"serde_json",
"sha1",
@ -3161,9 +3151,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.53"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
dependencies = [
"proc-macro2",
"quote",
@ -3558,7 +3548,7 @@ dependencies = [
"idna",
"matches",
"percent-encoding",
"serde 1.0.117",
"serde 1.0.118",
]
[[package]]
@ -3568,7 +3558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand",
"serde 1.0.117",
"serde 1.0.118",
]
[[package]]
@ -3650,7 +3640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
dependencies = [
"cfg-if 1.0.0",
"serde 1.0.117",
"serde 1.0.118",
"serde_json",
"wasm-bindgen-macro",
]

View File

@ -28,7 +28,7 @@ lemmy_websocket = { path = "./lemmy_websocket" }
diesel = "1.4.5"
diesel_migrations = "1.4.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-files = { version = "0.4.1", default-features = false }
@ -44,7 +44,7 @@ openssl = "0.10.30"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
tokio = "0.3.5"
sha2 = "0.9.2"
anyhow = "1.0.34"
anyhow = "1.0.35"
reqwest = { version = "0.10.9", features = ["json"] }
activitystreams = "0.7.0-alpha.8"
actix-rt = { version = "1.1.1", default-features = false }

View File

@ -1,23 +1,25 @@
# Summary
- [About](about.md)
- [Features](about_features.md)
- [Goals](about_goals.md)
- [Post and Comment Ranking](about_ranking.md)
- [Guide](about_guide.md)
- [Administration](administration.md)
- [Install with Docker](administration_install_docker.md)
- [Install with Ansible](administration_install_ansible.md)
- [Configuration](administration_configuration.md)
- [Backup and Restore](administration_backup_and_restore.md)
- [Federation](administration_federation.md)
- [Contributing](contributing.md)
- [Docker Development](contributing_docker_development.md)
- [Local Development](contributing_local_development.md)
- [Tests](contributing_tests.md)
- [Federation Development](contributing_federation_development.md)
- [Websocket/HTTP API](contributing_websocket_http_api.md)
- [Federation Overview](contributing_federation_overview.md)
- [ActivityPub API Outline](contributing_apub_api_outline.md)
- [Theming Guide](contributing_theming.md)
- [About](about/about.md)
- [Features](about/features.md)
- [Goals](about/goals.md)
- [Post and Comment Ranking](about/ranking.md)
- [Guide](about/guide.md)
- [Administration](administration/administration.md)
- [Install with Docker](administration/install_docker.md)
- [Install with Ansible](administration/install_ansible.md)
- [Configuration](administration/configuration.md)
- [Backup and Restore](administration/backup_and_restore.md)
- [Federation](federation/federation.md)
- [Federation Overview](federation/overview.md)
- [Administration](federation/administration.md)
- [Resources](federation/resources.md)
- [Lemmy Protocol](federation/lemmy_protocol.md)
- [Contributing](contributing/contributing.md)
- [Docker Development](contributing/docker_development.md)
- [Local Development](contributing/local_development.md)
- [Theming Guide](contributing/theming.md)
- [Tests](contributing/tests.md)
- [Websocket/HTTP API](contributing/websocket_http_api.md)
- [Federation Development](contributing/federation_development.md)
- [Lemmy Council](lemmy_council.md)

View File

@ -37,19 +37,3 @@
- [Zurb mentions](https://github.com/zurb/tribute)
- [TippyJS](https://github.com/atomiks/tippyjs)
- [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html)
## Activitypub guides
- https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/
- https://raw.githubusercontent.com/w3c/activitypub/gh-pages/activitypub-tutorial.txt
- https://github.com/tOkeshu/activitypub-example
- https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
- Use the [activitypub crate.](https://docs.rs/activitypub/0.1.4/activitypub/)
- https://docs.rs/activitypub/0.1.4/activitypub/
- [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/)
- [Activitypub main](https://www.w3.org/TR/activitypub/)
- [Federation.md](https://github.com/dariusk/gathio/blob/7fc93dbe9d4d99457a0e85c6c532112f415b7af2/FEDERATION.md)
- [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479)
- [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579/3)
- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood)
- [Asonix http signatures in rust](https://git.asonix.dog/Aardwolf/http-signature-normalization)

View File

@ -17,7 +17,7 @@ Hot | Trending sort based on the score, and the post creation time.
New | Newest items.
Top | The highest scoring items in the given time frame.
For more detail, check the [Post and Comment Ranking details](about_ranking.md).
For more detail, check the [Post and Comment Ranking details](ranking.md).
## Markdown Guide

View File

@ -4,7 +4,7 @@ Information for Lemmy instance admins, and those who want to run a server.
## Install
Lemmy has two primary install methods, [docker](administration_install_docker.md), and [ansible](administration_install_ansible.md). Ansible simplifies deploying to a remote server, while docker is best for local testing.
Lemmy has two primary install methods, [docker](install_docker.md), and [ansible](install_ansible.md). Ansible simplifies deploying to a remote server, while docker is best for local testing.
### Manual install

View File

@ -1,6 +1,6 @@
# Ansible Installation
This is the same as the [Docker installation](administration_install_docker.md), except that Ansible handles all of it automatically. It also does some extra things like setting up TLS and email for your Lemmy instance.
This is the same as the [Docker installation](install_docker.md), except that Ansible handles all of it automatically. It also does some extra things like setting up TLS and email for your Lemmy instance.
First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) (e.g. using `sudo apt install ansible`) or the equivalent for you platform.

View File

@ -27,7 +27,7 @@ Open up your `docker-compose.yml`, and make sure `LEMMY_EXTERNAL_HOST` for `lemm
If you'd like a different database password, you should also change it in the `docker-compose.yml` **before** your first run.
After this, have a look at the [config file](administration_configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname, and possibly the db password. Then run:
After this, have a look at the [config file](configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname, and possibly the db password. Then run:
`docker-compose up -d`

View File

@ -29,4 +29,4 @@ To speed up the Docker compile, add the following to `/etc/docker/daemon.json` a
```
If the build is still too slow, you will have to use a
[local build](contributing_local_development.md) instead.
[local build](local_development.md) instead.

View File

@ -2,7 +2,7 @@
## Running locally
Install the dependencies as described in [Docker development](contributing_docker_development.md). Then run the following
Install the dependencies as described in [Docker development](docker_development.md). Then run the following
```bash
cd docker/federation
@ -34,8 +34,8 @@ Firefox containers are a good way to test them interacting.
Note that federation is currently in alpha. **Only use it for testing**, not on any production server, and be aware that turning on federation may break your instance.
Follow the normal installation instructions, either with [Ansible](administration_install_ansible.md) or
[manually](administration_install_docker.md). Then replace the line `image: dessalines/lemmy:v0.x.x` in
Follow the normal installation instructions, either with [Ansible](../administration/install_ansible.md) or
[manually](../administration/install_docker.md). Then replace the line `image: dessalines/lemmy:v0.x.x` in
`/lemmy/docker-compose.yml` with `image: dessalines/lemmy:federation`. Also add the following in
`/lemmy/lemmy.hjson`:

View File

@ -85,4 +85,4 @@ and go to [localhost:1234](http://localhost:1234). Front end saves should rebuil
Note that this setup doesn't include image uploads or link previews (provided by pict-rs and
iframely respectively). If you want to test those, you should use the
[Docker development](contributing_docker_development.md).
[Docker development](docker_development.md).

View File

@ -2,7 +2,7 @@
#### Rust
After installing [local development dependencies](contributing_local_development.md), run the
After installing [local development dependencies](local_development.md), run the
following commands:
```bash
@ -12,7 +12,7 @@ psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
### Federation
Install the [Docker development dependencies](contributing_docker_development.md), and execute:
Install the [Docker development dependencies](docker_development.md), and execute:
```
cd docker/federation

View File

@ -1,4 +1,4 @@
# Federation
# Federation Administration
Note: ActivityPub federation is still under development. We recommend that you only enable it on test instances for now.
@ -11,6 +11,7 @@ Federation does not start automatically, but needs to be triggered manually thro
- `https://lemmy.ml/c/programming` (Community)
- `https://lemmy.ml/u/nutomic` (User)
- `https://lemmy.ml/post/123` (Post)
- `https://lemmy.ml/comment/321` (Comment)
For an overview of how federation in Lemmy works on a technical level, check out our [Federation Overview](contributing_federation_overview.md).

View File

@ -0,0 +1,14 @@
# Federation
Lemmy uses the ActivityPub protocol (a W3C standard) to enable federation between different servers (often called instances). This is very similar to the way email works. For example, if you use gmail.com, then you can't just send mails to other gmail.com users, but also to yahoo.com, yandex.ru and so on. Email uses the SMTP protocol to achieve this, so you can think of ActivityPub as "SMTP for social media". The amount of different actions possible on social media (post, comment, like, share, etc) means that ActivityPub is much more complicated than SMTP.
As with email, ActivityPub federation happens only between servers. So if you are registered on `enterprise.lemmy.ml`, you only connect to the API of `enterprise.lemmy.ml`, while the server takes care of sending and receiving data from other instances (eg `voyager.lemmy.ml`). The great advantage of this approach is that the average user doesn't have to do anything to use federation. In fact if you are using Lemmy, you are likely already using it. One way to confirm is by going to a community or user profile. If you are on `enterprise.lemmy.ml` and you see a user like `@nutomic@voyager.lemmy.ml`, or a community like `!main@ds9.lemmy.ml`, then those are federated, meaning they use a different instance from yours.
One way you can take advantage of federation is by opening a different instance, like `ds9.lemmy.ml`, and browsing it. If you see an interesting community, post or user that you want to interact with, just copy its URL and paste it into the search of your own instance. Your instance will connect to the other one (assuming the allowlist/blocklist allows it), and directly display the remote content to you, so that you can follow a community or comment on a post. Here are some examples of working searches:
- `!main@lemmy.ml` (Community)
- `@nutomic@lemmy.ml` (User)
- `https://lemmy.ml/c/programming` (Community)
- `https://lemmy.ml/u/nutomic` (User)
- `https://lemmy.ml/post/123` (Post)
- `https://lemmy.ml/comment/321` (Comment)

View File

@ -1,4 +1,6 @@
# Activitypub API outline
# Lemmy Federation Protocol
The Lemmy Protocol (or Lemmy Federation Protocol) is a strict subset of the [ActivityPub Protocol](https://www.w3.org/TR/activitypub/). Any deviation from the ActivityPub protocol is a bug in Lemmy or in this documentation (or both).
This document is targeted at developers who are familiar with the ActivityPub and ActivityStreams protocols. It gives a detailed outline of the actors, objects and activities used by Lemmy.
@ -438,7 +440,7 @@ Sent to: Community
"cc": [
"https://ds9.lemmy.ml/c/main/"
],
"object": ...
"object": "https://enterprise.lemmy.ml/p/123"
}
```
@ -465,7 +467,7 @@ Sent to: Community
"cc": [
"https://ds9.lemmy.ml/c/main/"
],
"object": ...
"object": "https://enterprise.lemmy.ml/p/123"
}
```

View File

@ -1,4 +1,4 @@
# Federation
# Federation Overview
This document is for anyone who wants to know how Lemmy federation works, without being overly technical. It is meant provide a high-level overview of ActivityPub federation in Lemmy. If you are implementing ActivityPub yourself and want to be compatible with Lemmy, read our [ActivityPub API outline](contributing_apub_api_outline.md).

View File

@ -0,0 +1,22 @@
# ActivityPub Resources
## Official Documents
- [ActivityPub standard](https://www.w3.org/TR/activitypub/)
- [Activitypub vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/)
## Explanations
- [ActivityPub - one protocol to rule them all?](https://schub.io/blog/2018/02/01/activitypub-one-protocol-to-rule-them-all.html)
- [A highly opinionated guide to learning about ActivityPub](https://tinysubversions.com/notes/reading-activitypub/)
- [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479)
- [Mastodon Blog: How to implement a basic ActivityPub server](https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/)
- [Mastodon Blog: Implementing an ActivityPub inbox](https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/)
- [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579)
- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood)
## Examples and Libraries
- [ActivityPub example server](https://github.com/tOkeshu/activitypub-example)
- [ActivityStreams crate](https://docs.rs/activitystreams/)
- [HTTP Signatures crate](https://git.asonix.dog/Aardwolf/http-signature-normalization)

View File

@ -19,7 +19,7 @@ diesel = "1.4.5"
bcrypt = "0.9.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false }
actix-rt = { version = "1.1.1", default-features = false }
@ -42,7 +42,7 @@ uuid = { version = "0.8.1", features = ["serde", "v4"] }
sha2 = "0.9.2"
async-trait = "0.1.42"
captcha = "0.0.8"
anyhow = "1.0.34"
anyhow = "1.0.35"
thiserror = "1.0.22"
background-jobs = "0.8.0"
reqwest = { version = "0.10.9", features = ["json"] }

View File

@ -24,6 +24,7 @@ use lemmy_db::{
community_view::{CommunityQueryBuilder, CommunityView},
user_view::UserViewSafe,
},
ApubObject,
Bannable,
Crud,
Followable,
@ -133,7 +134,7 @@ impl Perform for CreateCommunity {
let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
let actor_id_cloned = actor_id.to_owned();
let community_dupe = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, &actor_id_cloned)
Community::read_from_apub_id(conn, &actor_id_cloned)
})
.await?;
if community_dupe.is_ok() {

View File

@ -19,7 +19,7 @@ activitystreams-ext = "0.1.0-alpha.2"
bcrypt = "0.9.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
actix = "0.10.0"
actix-web = { version = "3.3.2", default-features = false }
actix-rt = { version = "1.1.1", default-features = false }
@ -42,7 +42,7 @@ itertools = "0.9.0"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
sha2 = "0.9.2"
async-trait = "0.1.42"
anyhow = "1.0.34"
anyhow = "1.0.35"
thiserror = "1.0.22"
background-jobs = "0.8.0"
reqwest = { version = "0.10.9", features = ["json"] }

View File

@ -1,20 +1,13 @@
use crate::{
activities::receive::get_actor_as_user,
fetcher::get_or_fetch_and_insert_comment,
ActorType,
FromApub,
NoteExt,
};
use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, NoteExt};
use activitystreams::{
activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update},
base::ExtendsExt,
};
use anyhow::{anyhow, Context};
use anyhow::Context;
use lemmy_db::{
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
comment::{Comment, CommentLike, CommentLikeForm},
comment_view::CommentView,
post::Post,
Crud,
Likeable,
};
use lemmy_structs::{blocking, comment::CommentResponse, send_local_notifs};
@ -30,36 +23,22 @@ pub(crate) async fn receive_create_comment(
let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let comment =
CommentForm::from_apub(&note, context, Some(user.actor_id()?), request_counter).await?;
let comment = Comment::from_apub(&note, context, user.actor_id()?, request_counter).await?;
let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
if post.locked {
return Err(anyhow!("Post is locked").into());
}
let inserted_comment =
blocking(context.pool(), move |conn| Comment::upsert(conn, &comment)).await??;
// Note:
// Although mentions could be gotten from the post tags (they are included there), or the ccs,
// Its much easier to scrape them from the comment body, since the API has to do that
// anyway.
let mentions = scrape_text_for_mentions(&inserted_comment.content);
let recipient_ids = send_local_notifs(
mentions,
inserted_comment.clone(),
&user,
post,
context.pool(),
true,
)
.await?;
let mentions = scrape_text_for_mentions(&comment.content);
let recipient_ids =
send_local_notifs(mentions, comment.clone(), &user, post, context.pool(), true).await?;
// Refetch the view
let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, inserted_comment.id, None)
CommentView::read(conn, comment.id, None)
})
.await??;
@ -87,36 +66,19 @@ pub(crate) async fn receive_update_comment(
.context(location_info!())?;
let user = get_actor_as_user(&update, context, request_counter).await?;
let comment =
CommentForm::from_apub(&note, context, Some(user.actor_id()?), request_counter).await?;
let comment = Comment::from_apub(&note, context, user.actor_id()?, request_counter).await?;
let original_comment_id =
get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
.await?
.id;
let updated_comment = blocking(context.pool(), move |conn| {
Comment::update(conn, original_comment_id, &comment)
})
.await??;
let post_id = updated_comment.post_id;
let comment_id = comment.id;
let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
let mentions = scrape_text_for_mentions(&updated_comment.content);
let recipient_ids = send_local_notifs(
mentions,
updated_comment,
&user,
post,
context.pool(),
false,
)
.await?;
let mentions = scrape_text_for_mentions(&comment.content);
let recipient_ids =
send_local_notifs(mentions, comment, &user, post, context.pool(), false).await?;
// Refetch the view
let comment_view = blocking(context.pool(), move |conn| {
CommentView::read(conn, original_comment_id, None)
CommentView::read(conn, comment_id, None)
})
.await??;
@ -137,19 +99,13 @@ pub(crate) async fn receive_update_comment(
pub(crate) async fn receive_like_comment(
like: Like,
comment: Comment,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let user = get_actor_as_user(&like, context, request_counter).await?;
let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
.await?
.id;
let comment_id = comment.id;
let like_form = CommentLikeForm {
comment_id,
post_id: comment.post_id,
@ -188,25 +144,13 @@ pub(crate) async fn receive_like_comment(
pub(crate) async fn receive_dislike_comment(
dislike: Dislike,
comment: Comment,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let note = NoteExt::from_any_base(
dislike
.object()
.to_owned()
.one()
.context(location_info!())?,
)?
.context(location_info!())?;
let user = get_actor_as_user(&dislike, context, request_counter).await?;
let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
.await?
.id;
let comment_id = comment.id;
let like_form = CommentLikeForm {
comment_id,
post_id: comment.post_id,

View File

@ -1,35 +1,23 @@
use crate::{
activities::receive::get_actor_as_user,
fetcher::get_or_fetch_and_insert_comment,
FromApub,
NoteExt,
};
use activitystreams::{activity::*, prelude::*};
use anyhow::Context;
use crate::activities::receive::get_actor_as_user;
use activitystreams::activity::{Dislike, Like};
use lemmy_db::{
comment::{Comment, CommentForm, CommentLike},
comment::{Comment, CommentLike},
comment_view::CommentView,
Likeable,
};
use lemmy_structs::{blocking, comment::CommentResponse};
use lemmy_utils::{location_info, LemmyError};
use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation};
pub(crate) async fn receive_undo_like_comment(
like: &Like,
comment: Comment,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let user = get_actor_as_user(like, context, request_counter).await?;
let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
.await?
.id;
let comment_id = comment.id;
let user_id = user.id;
blocking(context.pool(), move |conn| {
CommentLike::remove(conn, user_id, comment_id)
@ -61,25 +49,13 @@ pub(crate) async fn receive_undo_like_comment(
pub(crate) async fn receive_undo_dislike_comment(
dislike: &Dislike,
comment: Comment,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let user = get_actor_as_user(dislike, context, request_counter).await?;
let note = NoteExt::from_any_base(
dislike
.object()
.to_owned()
.one()
.context(location_info!())?,
)?
.context(location_info!())?;
let comment = CommentForm::from_apub(&note, context, None, request_counter).await?;
let comment_id = get_or_fetch_and_insert_comment(&comment.get_ap_id()?, context, request_counter)
.await?
.id;
let comment_id = comment.id;
let user_id = user.id;
blocking(context.pool(), move |conn| {
CommentLike::remove(conn, user_id, comment_id)

View File

@ -4,7 +4,7 @@ use activitystreams::{
base::{AnyBase, ExtendsExt},
};
use anyhow::Context;
use lemmy_db::{community::Community, views::community_view::CommunityView};
use lemmy_db::{community::Community, views::community_view::CommunityView, ApubObject};
use lemmy_structs::{blocking, community::CommunityResponse};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
@ -53,7 +53,7 @@ pub(crate) async fn receive_remove_community(
.single_xsd_any_uri()
.context(location_info!())?;
let community = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, community_uri.as_str())
Community::read_from_apub_id(conn, community_uri.as_str())
})
.await??;
@ -135,7 +135,7 @@ pub(crate) async fn receive_undo_remove_community(
.single_xsd_any_uri()
.context(location_info!())?;
let community = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, community_uri.as_str())
Community::read_from_apub_id(conn, community_uri.as_str())
})
.await??;

View File

@ -1,19 +1,12 @@
use crate::{
activities::receive::get_actor_as_user,
fetcher::get_or_fetch_and_insert_post,
ActorType,
FromApub,
PageExt,
};
use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt};
use activitystreams::{
activity::{Create, Dislike, Like, Remove, Update},
prelude::*,
};
use anyhow::Context;
use lemmy_db::{
post::{Post, PostForm, PostLike, PostLikeForm},
post::{Post, PostLike, PostLikeForm},
post_view::PostView,
Crud,
Likeable,
};
use lemmy_structs::{blocking, post::PostResponse};
@ -29,16 +22,12 @@ pub(crate) async fn receive_create_post(
let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?;
// Using an upsert, since likes (which fetch the post), sometimes come in before the create
// resulting in double posts.
let inserted_post = blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??;
let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
// Refetch the view
let inserted_post_id = inserted_post.id;
let post_id = post.id;
let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, inserted_post_id, None)
PostView::read(conn, post_id, None)
})
.await??;
@ -62,20 +51,12 @@ pub(crate) async fn receive_update_post(
let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?;
let original_post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
.await?
.id;
blocking(context.pool(), move |conn| {
Post::update(conn, original_post_id, &post)
})
.await??;
let post = Post::from_apub(&page, context, user.actor_id()?, request_counter).await?;
let post_id = post.id;
// Refetch the view
let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, original_post_id, None)
PostView::read(conn, post_id, None)
})
.await??;
@ -92,19 +73,13 @@ pub(crate) async fn receive_update_post(
pub(crate) async fn receive_like_post(
like: Like,
post: Post,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let user = get_actor_as_user(&like, context, request_counter).await?;
let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let post = PostForm::from_apub(&page, context, None, request_counter).await?;
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
.await?
.id;
let post_id = post.id;
let like_form = PostLikeForm {
post_id,
user_id: user.id,
@ -136,25 +111,13 @@ pub(crate) async fn receive_like_post(
pub(crate) async fn receive_dislike_post(
dislike: Dislike,
post: Post,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let user = get_actor_as_user(&dislike, context, request_counter).await?;
let page = PageExt::from_any_base(
dislike
.object()
.to_owned()
.one()
.context(location_info!())?,
)?
.context(location_info!())?;
let post = PostForm::from_apub(&page, context, None, request_counter).await?;
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
.await?
.id;
let post_id = post.id;
let like_form = PostLikeForm {
post_id,
user_id: user.id,

View File

@ -1,35 +1,23 @@
use crate::{
activities::receive::get_actor_as_user,
fetcher::get_or_fetch_and_insert_post,
FromApub,
PageExt,
};
use activitystreams::{activity::*, prelude::*};
use anyhow::Context;
use crate::activities::receive::get_actor_as_user;
use activitystreams::activity::{Dislike, Like};
use lemmy_db::{
post::{Post, PostForm, PostLike},
post::{Post, PostLike},
post_view::PostView,
Likeable,
};
use lemmy_structs::{blocking, post::PostResponse};
use lemmy_utils::{location_info, LemmyError};
use lemmy_utils::LemmyError;
use lemmy_websocket::{messages::SendPost, LemmyContext, UserOperation};
pub(crate) async fn receive_undo_like_post(
like: &Like,
post: Post,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let user = get_actor_as_user(like, context, request_counter).await?;
let page = PageExt::from_any_base(like.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?;
let post = PostForm::from_apub(&page, context, None, request_counter).await?;
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
.await?
.id;
let post_id = post.id;
let user_id = user.id;
blocking(context.pool(), move |conn| {
PostLike::remove(conn, user_id, post_id)
@ -55,25 +43,13 @@ pub(crate) async fn receive_undo_like_post(
pub(crate) async fn receive_undo_dislike_post(
dislike: &Dislike,
post: Post,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let user = get_actor_as_user(dislike, context, request_counter).await?;
let page = PageExt::from_any_base(
dislike
.object()
.to_owned()
.one()
.context(location_info!())?,
)?
.context(location_info!())?;
let post = PostForm::from_apub(&page, context, None, request_counter).await?;
let post_id = get_or_fetch_and_insert_post(&post.get_ap_id()?, context, request_counter)
.await?
.id;
let post_id = post.id;
let user_id = user.id;
blocking(context.pool(), move |conn| {
PostLike::remove(conn, user_id, post_id)

View File

@ -3,7 +3,7 @@ use crate::{
check_is_apub_id_valid,
fetcher::get_or_fetch_and_upsert_user,
inbox::get_activity_to_and_cc,
FromApub,
objects::FromApub,
NoteExt,
};
use activitystreams::{
@ -13,11 +13,7 @@ use activitystreams::{
public,
};
use anyhow::{anyhow, Context};
use lemmy_db::{
private_message::{PrivateMessage, PrivateMessageForm},
private_message_view::PrivateMessageView,
Crud,
};
use lemmy_db::{private_message::PrivateMessage, private_message_view::PrivateMessageView};
use lemmy_structs::{blocking, user::PrivateMessageResponse};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
@ -41,15 +37,10 @@ pub(crate) async fn receive_create_private_message(
.context(location_info!())?;
let private_message =
PrivateMessageForm::from_apub(&note, context, Some(expected_domain), request_counter).await?;
let inserted_private_message = blocking(&context.pool(), move |conn| {
PrivateMessage::create(conn, &private_message)
})
.await??;
PrivateMessage::from_apub(&note, context, expected_domain, request_counter).await?;
let message = blocking(&context.pool(), move |conn| {
PrivateMessageView::read(conn, inserted_private_message.id)
PrivateMessageView::read(conn, private_message.id)
})
.await??;
@ -82,24 +73,8 @@ pub(crate) async fn receive_update_private_message(
.to_owned();
let note = NoteExt::from_any_base(object)?.context(location_info!())?;
let private_message_form =
PrivateMessageForm::from_apub(&note, context, Some(expected_domain), request_counter).await?;
let private_message_ap_id = private_message_form
.ap_id
.as_ref()
.context(location_info!())?
.clone();
let private_message = blocking(&context.pool(), move |conn| {
PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
})
.await??;
let private_message_id = private_message.id;
blocking(&context.pool(), move |conn| {
PrivateMessage::update(conn, private_message_id, &private_message_form)
})
.await??;
let private_message =
PrivateMessage::from_apub(&note, context, expected_domain, request_counter).await?;
let private_message_id = private_message.id;
let message = blocking(&context.pool(), move |conn| {

View File

@ -3,10 +3,10 @@ use crate::{
activity_queue::{send_comment_mentions, send_to_community},
extensions::context::lemmy_context,
fetcher::get_or_fetch_and_upsert_user,
objects::ToApub,
ActorType,
ApubLikeableType,
ApubObjectType,
ToApub,
};
use activitystreams::{
activity::{
@ -218,8 +218,6 @@ impl ApubObjectType for Comment {
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Comment {
async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -229,7 +227,7 @@ impl ApubLikeableType for Comment {
})
.await??;
let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
like
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?)
@ -241,8 +239,6 @@ impl ApubLikeableType for Comment {
}
async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -252,7 +248,7 @@ impl ApubLikeableType for Comment {
})
.await??;
let mut dislike = Dislike::new(creator.actor_id.to_owned(), note.into_any_base()?);
let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
dislike
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DislikeType::Dislike)?)
@ -268,8 +264,6 @@ impl ApubLikeableType for Comment {
creator: &User_,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let note = self.to_apub(context.pool()).await?;
let post_id = self.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@ -279,7 +273,7 @@ impl ApubLikeableType for Comment {
})
.await??;
let mut like = Like::new(creator.actor_id.to_owned(), note.into_any_base()?);
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
like
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DislikeType::Dislike)?)

View File

@ -2,10 +2,10 @@ use crate::{
activities::send::generate_activity_id,
activity_queue::send_to_community,
extensions::context::lemmy_context,
objects::ToApub,
ActorType,
ApubLikeableType,
ApubObjectType,
ToApub,
};
use activitystreams::{
activity::{
@ -167,15 +167,13 @@ impl ApubObjectType for Post {
#[async_trait::async_trait(?Send)]
impl ApubLikeableType for Post {
async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
like
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?)
@ -187,15 +185,13 @@ impl ApubLikeableType for Post {
}
async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let mut dislike = Dislike::new(creator.actor_id.to_owned(), page.into_any_base()?);
let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
dislike
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DislikeType::Dislike)?)
@ -211,15 +207,13 @@ impl ApubLikeableType for Post {
creator: &User_,
context: &LemmyContext,
) -> Result<(), LemmyError> {
let page = self.to_apub(context.pool()).await?;
let community_id = self.community_id;
let community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let mut like = Like::new(creator.actor_id.to_owned(), page.into_any_base()?);
let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
like
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?)

View File

@ -2,9 +2,9 @@ use crate::{
activities::send::generate_activity_id,
activity_queue::send_activity_single_dest,
extensions::context::lemmy_context,
objects::ToApub,
ActorType,
ApubObjectType,
ToApub,
};
use activitystreams::{
activity::{

View File

@ -16,6 +16,7 @@ use activitystreams::{
use lemmy_db::{
community::{Community, CommunityFollower, CommunityFollowerForm},
user::User_,
ApubObject,
DbPool,
Followable,
};
@ -46,7 +47,7 @@ impl ActorType for User_ {
) -> Result<(), LemmyError> {
let follow_actor_id = follow_actor_id.to_string();
let community = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, &follow_actor_id)
Community::read_from_apub_id(conn, &follow_actor_id)
})
.await??;
@ -77,7 +78,7 @@ impl ActorType for User_ {
) -> Result<(), LemmyError> {
let follow_actor_id = follow_actor_id.to_string();
let community = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, &follow_actor_id)
Community::read_from_apub_id(conn, &follow_actor_id)
})
.await??;

View File

@ -33,7 +33,7 @@ use url::Url;
/// * `activity` the apub activity to be sent
/// * `creator` the local actor which created the activity
/// * `inbox` the inbox url where the activity should be delivered to
pub async fn send_activity_single_dest<T, Kind>(
pub(crate) async fn send_activity_single_dest<T, Kind>(
activity: T,
creator: &dyn ActorType,
inbox: Url,
@ -71,7 +71,7 @@ where
/// * `community` the sending community
/// * `sender_shared_inbox` in case of an announce, this should be the shared inbox of the inner
/// activities creator, as receiving a known activity will cause an error
pub async fn send_to_community_followers<T, Kind>(
pub(crate) async fn send_to_community_followers<T, Kind>(
activity: T,
community: &Community,
context: &LemmyContext,
@ -116,7 +116,7 @@ where
/// * `creator` the creator of the activity
/// * `community` the destination community
///
pub async fn send_to_community<T, Kind>(
pub(crate) async fn send_to_community<T, Kind>(
activity: T,
creator: &User_,
community: &Community,
@ -160,7 +160,7 @@ where
/// * `creator` user who created the comment
/// * `mentions` list of inboxes of users which are mentioned in the comment
/// * `activity` either a `Create/Note` or `Update/Note`
pub async fn send_comment_mentions<T, Kind>(
pub(crate) async fn send_comment_mentions<T, Kind>(
creator: &User_,
mentions: Vec<Url>,
activity: T,

View File

@ -65,7 +65,10 @@ pub async fn sign_and_send(
}
/// Verifies the HTTP signature on an incoming inbox request.
pub fn verify_signature(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> {
pub(crate) fn verify_signature(
request: &HttpRequest,
actor: &dyn ActorType,
) -> Result<(), LemmyError> {
let public_key = actor.public_key().context(location_info!())?;
let verified = CONFIG2
.begin_verify(

View File

@ -1,7 +1,7 @@
use crate::{
check_is_apub_id_valid,
objects::FromApub,
ActorType,
FromApub,
GroupExt,
NoteExt,
PageExt,
@ -13,15 +13,15 @@ use anyhow::{anyhow, Context};
use chrono::NaiveDateTime;
use diesel::result::Error::NotFound;
use lemmy_db::{
comment::{Comment, CommentForm},
comment::Comment,
comment_view::CommentView,
community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
community::{Community, CommunityModerator, CommunityModeratorForm},
naive_now,
post::{Post, PostForm},
post::Post,
post_view::PostView,
user::{UserForm, User_},
user::User_,
views::{community_view::CommunityView, user_view::UserViewSafe},
Crud,
ApubObject,
Joinable,
SearchType,
};
@ -183,22 +183,16 @@ pub async fn search_by_apub_id(
response
}
SearchAcceptedObjects::Page(p) => {
let post_form = PostForm::from_apub(&p, context, Some(query_url), recursion_counter).await?;
let p = Post::from_apub(&p, context, query_url, recursion_counter).await?;
let p = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
response.posts =
vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
response
}
SearchAcceptedObjects::Comment(c) => {
let comment_form =
CommentForm::from_apub(&c, context, Some(query_url), recursion_counter).await?;
let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?;
let c = blocking(context.pool(), move |conn| {
Comment::upsert(conn, &comment_form)
})
.await??;
response.comments = vec![
blocking(context.pool(), move |conn| {
CommentView::read(conn, c.id, None)
@ -242,7 +236,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user(
) -> Result<User_, LemmyError> {
let apub_id_owned = apub_id.to_owned();
let user = blocking(context.pool(), move |conn| {
User_::read_from_actor_id(conn, apub_id_owned.as_ref())
User_::read_from_apub_id(conn, apub_id_owned.as_ref())
})
.await?;
@ -257,15 +251,13 @@ pub(crate) async fn get_or_fetch_and_upsert_user(
return Ok(u);
}
let mut uf = UserForm::from_apub(
&person?,
context,
Some(apub_id.to_owned()),
recursion_counter,
)
.await?;
uf.last_refreshed_at = Some(naive_now());
let user = blocking(context.pool(), move |conn| User_::update(conn, u.id, &uf)).await??;
let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
let user_id = user.id;
blocking(context.pool(), move |conn| {
User_::mark_as_updated(conn, user_id)
})
.await??;
Ok(user)
}
@ -275,14 +267,7 @@ pub(crate) async fn get_or_fetch_and_upsert_user(
let person =
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
let uf = UserForm::from_apub(
&person,
context,
Some(apub_id.to_owned()),
recursion_counter,
)
.await?;
let user = blocking(context.pool(), move |conn| User_::upsert(conn, &uf)).await??;
let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
Ok(user)
}
@ -317,7 +302,7 @@ pub(crate) async fn get_or_fetch_and_upsert_community(
) -> Result<Community, LemmyError> {
let apub_id_owned = apub_id.to_owned();
let community = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, apub_id_owned.as_str())
Community::read_from_apub_id(conn, apub_id_owned.as_str())
})
.await?;
@ -353,9 +338,8 @@ async fn fetch_remote_community(
}
let group = group?;
let cf =
CommunityForm::from_apub(&group, context, Some(apub_id.to_owned()), recursion_counter).await?;
let community = blocking(context.pool(), move |conn| Community::upsert(conn, &cf)).await??;
let community =
Community::from_apub(&group, context, apub_id.to_owned(), recursion_counter).await?;
// Also add the community moderators too
let attributed_to = group.inner.attributed_to().context(location_info!())?;
@ -405,23 +389,13 @@ async fn fetch_remote_community(
}
for o in outbox_items {
let page = PageExt::from_any_base(o)?.context(location_info!())?;
let page_id = page.id_unchecked().context(location_info!())?;
// The post creator may be from a blocked instance,
// if it errors, then continue
let post = match PostForm::from_apub(&page, context, None, recursion_counter).await {
Ok(post) => post,
Err(_) => continue,
};
let post_ap_id = post.ap_id.as_ref().context(location_info!())?.clone();
// Check whether the post already exists in the local db
let existing = blocking(context.pool(), move |conn| {
Post::read_from_apub_id(conn, &post_ap_id)
})
.await?;
match existing {
Ok(e) => blocking(context.pool(), move |conn| Post::update(conn, e.id, &post)).await??,
Err(_) => blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??,
};
// The post creator may be from a blocked instance, if it errors, then skip it
if check_is_apub_id_valid(page_id).is_err() {
continue;
}
Post::from_apub(&page, context, page_id.to_owned(), recursion_counter).await?;
// TODO: we need to send a websocket update here
}
@ -447,17 +421,9 @@ pub(crate) async fn get_or_fetch_and_insert_post(
Ok(p) => Ok(p),
Err(NotFound {}) => {
debug!("Fetching and creating remote post: {}", post_ap_id);
let post =
let page =
fetch_remote_object::<PageExt>(context.client(), post_ap_id, recursion_counter).await?;
let post_form = PostForm::from_apub(
&post,
context,
Some(post_ap_id.to_owned()),
recursion_counter,
)
.await?;
let post = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
let post = Post::from_apub(&page, context, post_ap_id.to_owned(), recursion_counter).await?;
Ok(post)
}
@ -489,25 +455,20 @@ pub(crate) async fn get_or_fetch_and_insert_comment(
);
let comment =
fetch_remote_object::<NoteExt>(context.client(), comment_ap_id, recursion_counter).await?;
let comment_form = CommentForm::from_apub(
let comment = Comment::from_apub(
&comment,
context,
Some(comment_ap_id.to_owned()),
comment_ap_id.to_owned(),
recursion_counter,
)
.await?;
let post_id = comment_form.post_id;
let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
if post.locked {
return Err(anyhow!("Post is locked").into());
}
let comment = blocking(context.pool(), move |conn| {
Comment::upsert(conn, &comment_form)
})
.await??;
Ok(comment)
}
Err(e) => Err(e.into()),

View File

@ -1,6 +1,6 @@
use crate::{
http::{create_apub_response, create_apub_tombstone_response},
ToApub,
objects::ToApub,
};
use actix_web::{body::Body, web, web::Path, HttpResponse};
use diesel::result::Error::NotFound;

View File

@ -1,8 +1,8 @@
use crate::{
extensions::context::lemmy_context,
http::{create_apub_response, create_apub_tombstone_response},
objects::ToApub,
ActorType,
ToApub,
};
use activitystreams::{
base::{AnyBase, BaseExt, ExtendsExt},

View File

@ -1,6 +1,6 @@
use crate::{
http::{create_apub_response, create_apub_tombstone_response},
ToApub,
objects::ToApub,
};
use actix_web::{body::Body, web, HttpResponse};
use diesel::result::Error::NotFound;

View File

@ -1,4 +1,9 @@
use crate::{extensions::context::lemmy_context, http::create_apub_response, ActorType, ToApub};
use crate::{
extensions::context::lemmy_context,
http::create_apub_response,
objects::ToApub,
ActorType,
};
use activitystreams::{
base::BaseExt,
collection::{CollectionExt, OrderedCollection},

View File

@ -30,6 +30,7 @@ use lemmy_db::{
community::{Community, CommunityFollower, CommunityFollowerForm},
user::User_,
views::community_user_ban_view::CommunityUserBanView,
ApubObject,
DbPool,
Followable,
};
@ -118,7 +119,7 @@ pub(crate) async fn community_receive_message(
// unconditionally.
let actor_id = actor.actor_id_str();
let user = blocking(&context.pool(), move |conn| {
User_::read_from_actor_id(&conn, &actor_id)
User_::read_from_apub_id(&conn, &actor_id)
})
.await??;
check_community_or_site_ban(&user, &to_community, context.pool()).await?;
@ -242,7 +243,7 @@ async fn handle_undo_follow(
verify_activity_domains_valid(&follow, &user_url, false)?;
let user = blocking(&context.pool(), move |conn| {
User_::read_from_actor_id(&conn, user_url.as_str())
User_::read_from_apub_id(&conn, user_url.as_str())
})
.await??;
let community_follower_form = CommunityFollowerForm {
@ -260,7 +261,7 @@ async fn handle_undo_follow(
Ok(())
}
async fn check_community_or_site_ban(
pub(crate) async fn check_community_or_site_ban(
user: &User_,
community: &Community,
pool: &DbPool,

View File

@ -12,7 +12,7 @@ use activitystreams::{
};
use actix_web::HttpRequest;
use anyhow::{anyhow, Context};
use lemmy_db::{activity::Activity, community::Community, user::User_, DbPool};
use lemmy_db::{activity::Activity, community::Community, user::User_, ApubObject, DbPool};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
@ -119,7 +119,7 @@ pub(crate) async fn is_addressed_to_local_user(
) -> Result<bool, LemmyError> {
for url in to_and_cc {
let url = url.to_string();
let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?;
let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?;
if let Ok(u) = user {
if u.local {
return Ok(true);
@ -141,7 +141,7 @@ pub(crate) async fn is_addressed_to_community_followers(
if url.ends_with("/followers") {
let community_url = url.replace("/followers", "");
let community = blocking(&pool, move |conn| {
Community::read_from_actor_id(&conn, &community_url)
Community::read_from_apub_id(&conn, &community_url)
})
.await??;
if !community.local {

View File

@ -31,6 +31,7 @@ use crate::{
receive_unhandled_activity,
verify_activity_domains_valid,
},
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::is_addressed_to_public,
};
use activitystreams::{
@ -40,7 +41,7 @@ use activitystreams::{
};
use anyhow::Context;
use diesel::result::Error::NotFound;
use lemmy_db::{comment::Comment, post::Post, site::Site, Crud};
use lemmy_db::{comment::Comment, post::Post, site::Site, ApubObject, Crud};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
@ -96,10 +97,12 @@ pub(in crate::inbox) async fn receive_like_for_community(
verify_activity_domains_valid(&like, &expected_domain, false)?;
is_addressed_to_public(&like)?;
match like.object().as_single_kind_str() {
Some("Page") => receive_like_post(like, context, request_counter).await,
Some("Note") => receive_like_comment(like, context, request_counter).await,
_ => receive_unhandled_activity(like),
let object_id = get_like_object_id(&like)?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
PostOrComment::Comment(comment) => {
receive_like_comment(like, comment, context, request_counter).await
}
}
}
@ -122,10 +125,14 @@ pub(in crate::inbox) async fn receive_dislike_for_community(
verify_activity_domains_valid(&dislike, &expected_domain, false)?;
is_addressed_to_public(&dislike)?;
match dislike.object().as_single_kind_str() {
Some("Page") => receive_dislike_post(dislike, context, request_counter).await,
Some("Note") => receive_dislike_comment(dislike, context, request_counter).await,
_ => receive_unhandled_activity(dislike),
let object_id = get_like_object_id(&dislike)?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => {
receive_dislike_post(dislike, post, context, request_counter).await
}
PostOrComment::Comment(comment) => {
receive_dislike_comment(dislike, comment, context, request_counter).await
}
}
}
@ -275,14 +282,14 @@ pub(in crate::inbox) async fn receive_undo_like_for_community(
verify_activity_domains_valid(&like, &expected_domain, false)?;
is_addressed_to_public(&like)?;
let type_ = like
.object()
.as_single_kind_str()
.context(location_info!())?;
match type_ {
"Note" => receive_undo_like_comment(&like, context, request_counter).await,
"Page" => receive_undo_like_post(&like, context, request_counter).await,
_ => receive_unhandled_activity(like),
let object_id = get_like_object_id(&like)?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => {
receive_undo_like_post(&like, post, context, request_counter).await
}
PostOrComment::Comment(comment) => {
receive_undo_like_comment(&like, comment, context, request_counter).await
}
}
}
@ -298,14 +305,14 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
verify_activity_domains_valid(&dislike, &expected_domain, false)?;
is_addressed_to_public(&dislike)?;
let type_ = dislike
.object()
.as_single_kind_str()
.context(location_info!())?;
match type_ {
"Note" => receive_undo_dislike_comment(&dislike, context, request_counter).await,
"Page" => receive_undo_dislike_post(&dislike, context, request_counter).await,
_ => receive_unhandled_activity(dislike),
let object_id = get_like_object_id(&dislike)?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => {
receive_undo_dislike_post(&dislike, post, context, request_counter).await
}
PostOrComment::Comment(comment) => {
receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
}
}
}
@ -341,3 +348,42 @@ async fn find_post_or_comment_by_id(
return Err(NotFound.into());
}
async fn fetch_post_or_comment_by_id(
apub_id: &Url,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<PostOrComment, LemmyError> {
if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
return Ok(PostOrComment::Post(post));
}
if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
return Ok(PostOrComment::Comment(comment));
}
return Err(NotFound.into());
}
fn get_like_object_id<Activity>(like_or_dislike: &Activity) -> Result<Url, LemmyError>
where
Activity: ActorAndObjectRefExt,
{
// TODO: For backwards compatibility with older Lemmy versions where like.object contains a full
// post/comment. This can be removed after some time, using
// `activity.oject().as_single_xsd_any_uri()` instead.
let object = like_or_dislike.object();
if let Some(xsd_uri) = object.as_single_xsd_any_uri() {
Ok(xsd_uri.to_owned())
} else {
Ok(
object
.to_owned()
.one()
.context(location_info!())?
.id()
.context(location_info!())?
.to_owned(),
)
}
}

View File

@ -15,7 +15,7 @@ use crate::{
use activitystreams::{activity::ActorAndObject, prelude::*};
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::Context;
use lemmy_db::{community::Community, DbPool};
use lemmy_db::{community::Community, ApubObject, DbPool};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
@ -137,10 +137,7 @@ async fn extract_local_community_from_destinations(
) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc {
let url = url.to_string();
let community = blocking(&pool, move |conn| {
Community::read_from_actor_id(&conn, &url)
})
.await?;
let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?;
if let Ok(c) = community {
if c.local {
return Ok(Some(c));

View File

@ -52,6 +52,7 @@ use lemmy_db::{
community::{Community, CommunityFollower},
private_message::PrivateMessage,
user::User_,
ApubObject,
Followable,
};
use lemmy_structs::blocking;
@ -377,7 +378,7 @@ async fn find_community_or_private_message_by_id(
) -> Result<CommunityOrPrivateMessage, LemmyError> {
let ap_id = apub_id.to_string();
let community = blocking(context.pool(), move |conn| {
Community::read_from_actor_id(conn, &ap_id)
Community::read_from_apub_id(conn, &ap_id)
})
.await?;
if let Ok(c) = community {

View File

@ -18,7 +18,7 @@ use activitystreams::{
activity::Follow,
actor::{ApActor, Group, Person},
base::AnyBase,
object::{ApObject, Note, Page, Tombstone},
object::{ApObject, Note, Page},
};
use activitystreams_ext::{Ext1, Ext2};
use anyhow::{anyhow, Context};
@ -109,33 +109,6 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> {
}
}
/// Trait for converting an object or actor into the respective ActivityPub type.
#[async_trait::async_trait(?Send)]
pub trait ToApub {
type ApubType;
async fn to_apub(&self, pool: &DbPool) -> Result<Self::ApubType, LemmyError>;
fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
}
#[async_trait::async_trait(?Send)]
pub trait FromApub {
type ApubType;
/// Converts an object from ActivityPub type to Lemmy internal type.
///
/// * `apub` The object to read from
/// * `context` LemmyContext which holds DB pool, HTTP client etc
/// * `expected_domain` If present, ensure that the domains of this and of the apub object ID are
/// identical
async fn from_apub(
apub: &Self::ApubType,
context: &LemmyContext,
expected_domain: Option<Url>,
request_counter: &mut i32,
) -> Result<Self, LemmyError>
where
Self: Sized;
}
/// Common functions for ActivityPub objects, which are implemented by most (but not all) objects
/// and actors in Lemmy.
#[async_trait::async_trait(?Send)]
@ -248,7 +221,7 @@ pub trait ActorType {
/// Store a sent or received activity in the database, for logging purposes. These records are not
/// persistent.
pub async fn insert_activity<T>(
pub(crate) async fn insert_activity<T>(
ap_id: &Url,
activity: T,
local: bool,

View File

@ -7,19 +7,22 @@ use crate::{
},
objects::{
check_object_domain,
check_object_for_community_or_site_ban,
create_tombstone,
get_object_from_apub,
get_source_markdown_value,
set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
},
FromApub,
NoteExt,
ToApub,
};
use activitystreams::{
object::{kind::NoteType, ApObject, Note, Tombstone},
prelude::*,
};
use anyhow::Context;
use anyhow::{anyhow, Context};
use lemmy_db::{
comment::{Comment, CommentForm},
community::Community,
@ -87,16 +90,45 @@ impl ToApub for Comment {
}
#[async_trait::async_trait(?Send)]
impl FromApub for CommentForm {
impl FromApub for Comment {
type ApubType = NoteExt;
/// Converts a `Note` to `CommentForm`.
/// Converts a `Note` to `Comment`.
///
/// If the parent community, post and comment(s) are not known locally, these are also fetched.
async fn from_apub(
note: &NoteExt,
context: &LemmyContext,
expected_domain: Option<Url>,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<Comment, LemmyError> {
check_object_for_community_or_site_ban(note, context, request_counter).await?;
let comment: Comment =
get_object_from_apub(note, context, expected_domain, request_counter).await?;
let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
if post.locked {
// This is not very efficient because a comment gets inserted just to be deleted right
// afterwards, but it seems to be the easiest way to implement it.
blocking(context.pool(), move |conn| {
Comment::delete(conn, comment.id)
})
.await??;
return Err(anyhow!("Post is locked").into());
} else {
Ok(comment)
}
}
}
#[async_trait::async_trait(?Send)]
impl FromApubToForm<NoteExt> for CommentForm {
async fn from_apub(
note: &NoteExt,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<CommentForm, LemmyError> {
let creator_actor_id = &note

View File

@ -4,13 +4,15 @@ use crate::{
objects::{
check_object_domain,
create_tombstone,
get_object_from_apub,
get_source_markdown_value,
set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
},
ActorType,
FromApub,
GroupExt,
ToApub,
};
use activitystreams::{
actor::{kind::GroupType, ApActor, Endpoints, Group},
@ -109,14 +111,28 @@ impl ToApub for Community {
create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
}
}
#[async_trait::async_trait(?Send)]
impl FromApub for CommunityForm {
impl FromApub for Community {
type ApubType = GroupExt;
/// Converts a `Group` to `Community`.
async fn from_apub(
group: &GroupExt,
context: &LemmyContext,
expected_domain: Option<Url>,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<Community, LemmyError> {
get_object_from_apub(group, context, expected_domain, request_counter).await
}
}
#[async_trait::async_trait(?Send)]
impl FromApubToForm<GroupExt> for CommunityForm {
async fn from_apub(
group: &GroupExt,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<Self, LemmyError> {
let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;

View File

@ -1,4 +1,8 @@
use crate::check_is_apub_id_valid;
use crate::{
check_is_apub_id_valid,
fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
inbox::community_inbox::check_community_or_site_ban,
};
use activitystreams::{
base::{AsBase, BaseExt, ExtendsExt},
markers::Base,
@ -7,7 +11,10 @@ use activitystreams::{
};
use anyhow::{anyhow, Context};
use chrono::NaiveDateTime;
use lemmy_utils::{location_info, utils::convert_datetime, LemmyError};
use lemmy_db::{ApubObject, Crud, DbPool};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, utils::convert_datetime, LemmyError};
use lemmy_websocket::LemmyContext;
use url::Url;
pub(crate) mod comment;
@ -16,6 +23,44 @@ pub(crate) mod post;
pub(crate) mod private_message;
pub(crate) mod user;
/// Trait for converting an object or actor into the respective ActivityPub type.
#[async_trait::async_trait(?Send)]
pub(crate) trait ToApub {
type ApubType;
async fn to_apub(&self, pool: &DbPool) -> Result<Self::ApubType, LemmyError>;
fn to_tombstone(&self) -> Result<Tombstone, LemmyError>;
}
#[async_trait::async_trait(?Send)]
pub(crate) trait FromApub {
type ApubType;
/// Converts an object from ActivityPub type to Lemmy internal type.
///
/// * `apub` The object to read from
/// * `context` LemmyContext which holds DB pool, HTTP client etc
/// * `expected_domain` Domain where the object was received from
async fn from_apub(
apub: &Self::ApubType,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<Self, LemmyError>
where
Self: Sized;
}
#[async_trait::async_trait(?Send)]
pub(in crate::objects) trait FromApubToForm<ApubType> {
async fn from_apub(
apub: &ApubType,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<Self, LemmyError>
where
Self: Sized;
}
/// Updated is actually the deletion time
fn create_tombstone<T>(
deleted: bool,
@ -43,17 +88,13 @@ where
pub(in crate::objects) fn check_object_domain<T, Kind>(
apub: &T,
expected_domain: Option<Url>,
expected_domain: Url,
) -> Result<String, LemmyError>
where
T: Base + AsBase<Kind>,
{
let object_id = if let Some(url) = expected_domain {
let domain = url.domain().context(location_info!())?;
apub.id(domain)?.context(location_info!())?
} else {
apub.id_unchecked().context(location_info!())?
};
let domain = expected_domain.domain().context(location_info!())?;
let object_id = apub.id(domain)?.context(location_info!())?;
check_is_apub_id_valid(&object_id)?;
Ok(object_id.to_string())
}
@ -127,3 +168,60 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L
Ok(())
}
}
/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object
/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise
/// the apub object is parsed, inserted and returned.
pub(in crate::objects) async fn get_object_from_apub<From, Kind, To, ToForm>(
from: &From,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<To, LemmyError>
where
From: BaseExt<Kind>,
To: ApubObject<ToForm> + Crud<ToForm> + Send + 'static,
ToForm: FromApubToForm<From> + Send + 'static,
{
let object_id = from.id_unchecked().context(location_info!())?.to_owned();
let domain = object_id.domain().context(location_info!())?;
// if its a local object, return it directly from the database
if Settings::get().hostname == domain {
let object = blocking(context.pool(), move |conn| {
To::read_from_apub_id(conn, object_id.as_str())
})
.await??;
Ok(object)
}
// otherwise parse and insert, assuring that it comes from the right domain
else {
let to_form = ToForm::from_apub(&from, context, expected_domain, request_counter).await?;
let to = blocking(context.pool(), move |conn| To::upsert(conn, &to_form)).await??;
Ok(to)
}
}
pub(in crate::objects) async fn check_object_for_community_or_site_ban<T, Kind>(
object: &T,
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<(), LemmyError>
where
T: ObjectExt<Kind>,
{
let user_id = object
.attributed_to()
.context(location_info!())?
.as_single_xsd_any_uri()
.context(location_info!())?;
let user = get_or_fetch_and_upsert_user(user_id, context, request_counter).await?;
let community_id = object
.to()
.context(location_info!())?
.as_single_xsd_any_uri()
.context(location_info!())?;
let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
check_community_or_site_ban(&user, &community, context.pool()).await
}

View File

@ -3,13 +3,16 @@ use crate::{
fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
objects::{
check_object_domain,
check_object_for_community_or_site_ban,
create_tombstone,
get_object_from_apub,
get_source_markdown_value,
set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
},
FromApub,
PageExt,
ToApub,
};
use activitystreams::{
object::{kind::PageType, ApObject, Image, Page, Tombstone},
@ -17,7 +20,6 @@ use activitystreams::{
};
use activitystreams_ext::Ext1;
use anyhow::Context;
use backtrace::Backtrace;
use lemmy_db::{
community::Community,
post::{Post, PostForm},
@ -33,7 +35,6 @@ use lemmy_utils::{
LemmyError,
};
use lemmy_websocket::LemmyContext;
use log::error;
use url::Url;
#[async_trait::async_trait(?Send)]
@ -98,7 +99,7 @@ impl ToApub for Post {
}
#[async_trait::async_trait(?Send)]
impl FromApub for PostForm {
impl FromApub for Post {
type ApubType = PageExt;
/// Converts a `PageExt` to `PostForm`.
@ -107,7 +108,20 @@ impl FromApub for PostForm {
async fn from_apub(
page: &PageExt,
context: &LemmyContext,
expected_domain: Option<Url>,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<Post, LemmyError> {
check_object_for_community_or_site_ban(page, context, request_counter).await?;
get_object_from_apub(page, context, expected_domain, request_counter).await
}
}
#[async_trait::async_trait(?Send)]
impl FromApubToForm<PageExt> for PostForm {
async fn from_apub(
page: &PageExt,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<PostForm, LemmyError> {
let ext = &page.ext_one;
@ -132,15 +146,6 @@ impl FromApub for PostForm {
let community =
get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?;
if community.local && creator.local {
let page_id = page.id_unchecked().context(location_info!())?;
let bt = Backtrace::new();
error!(
"Lemmy is parsing a local post as remote, page id: {}, stack trace: {:?}",
page_id, bt
);
}
let thumbnail_url = match &page.inner.image() {
Some(any_image) => Image::from_any_base(
any_image

View File

@ -5,12 +5,14 @@ use crate::{
objects::{
check_object_domain,
create_tombstone,
get_object_from_apub,
get_source_markdown_value,
set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
},
FromApub,
NoteExt,
ToApub,
};
use activitystreams::{
object::{kind::NoteType, ApObject, Note, Tombstone},
@ -63,13 +65,25 @@ impl ToApub for PrivateMessage {
}
#[async_trait::async_trait(?Send)]
impl FromApub for PrivateMessageForm {
impl FromApub for PrivateMessage {
type ApubType = NoteExt;
async fn from_apub(
note: &NoteExt,
context: &LemmyContext,
expected_domain: Option<Url>,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<PrivateMessage, LemmyError> {
get_object_from_apub(note, context, expected_domain, request_counter).await
}
}
#[async_trait::async_trait(?Send)]
impl FromApubToForm<NoteExt> for PrivateMessageForm {
async fn from_apub(
note: &NoteExt,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<PrivateMessageForm, LemmyError> {
let creator_actor_id = note

View File

@ -1,10 +1,15 @@
use crate::{
extensions::context::lemmy_context,
objects::{check_object_domain, get_source_markdown_value, set_content_and_source},
objects::{
check_object_domain,
get_source_markdown_value,
set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
},
ActorType,
FromApub,
PersonExt,
ToApub,
};
use activitystreams::{
actor::{ApActor, Endpoints, Person},
@ -16,10 +21,13 @@ use anyhow::Context;
use lemmy_db::{
naive_now,
user::{UserForm, User_},
ApubObject,
DbPool,
};
use lemmy_structs::blocking;
use lemmy_utils::{
location_info,
settings::Settings,
utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError,
};
@ -81,13 +89,38 @@ impl ToApub for User_ {
}
#[async_trait::async_trait(?Send)]
impl FromApub for UserForm {
impl FromApub for User_ {
type ApubType = PersonExt;
async fn from_apub(
person: &PersonExt,
context: &LemmyContext,
expected_domain: Url,
request_counter: &mut i32,
) -> Result<User_, LemmyError> {
let user_id = person.id_unchecked().context(location_info!())?.to_owned();
let domain = user_id.domain().context(location_info!())?;
if domain == Settings::get().hostname {
let user = blocking(context.pool(), move |conn| {
User_::read_from_apub_id(conn, user_id.as_str())
})
.await??;
Ok(user)
} else {
let user_form =
UserForm::from_apub(person, context, expected_domain, request_counter).await?;
let user = blocking(context.pool(), move |conn| User_::upsert(conn, &user_form)).await??;
Ok(user)
}
}
}
#[async_trait::async_trait(?Send)]
impl FromApubToForm<PersonExt> for UserForm {
async fn from_apub(
person: &PersonExt,
_context: &LemmyContext,
expected_domain: Option<Url>,
expected_domain: Url,
_request_counter: &mut i32,
) -> Result<Self, LemmyError> {
let avatar = match person.icon() {

View File

@ -11,7 +11,7 @@ path = "src/lib.rs"
lemmy_utils = { path = "../lemmy_utils" }
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
strum = "0.20.0"
strum_macros = "0.20.1"

View File

@ -2,6 +2,7 @@ use super::post::Post;
use crate::{
naive_now,
schema::{comment, comment_like, comment_saved},
ApubObject,
Crud,
Likeable,
Saveable,
@ -86,6 +87,23 @@ impl Crud<CommentForm> for Comment {
}
}
impl ApubObject<CommentForm> for Comment {
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
}
fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
insert_into(comment)
.values(comment_form)
.on_conflict(ap_id)
.do_update()
.set(comment_form)
.get_result::<Self>(conn)
}
}
impl Comment {
pub fn update_ap_id(
conn: &PgConnection,
@ -99,11 +117,6 @@ impl Comment {
.get_result::<Self>(conn)
}
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
}
pub fn permadelete_for_creator(
conn: &PgConnection,
for_creator_id: i32,
@ -168,16 +181,6 @@ impl Comment {
.set((content.eq(new_content), updated.eq(naive_now())))
.get_result::<Self>(conn)
}
pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
use crate::schema::comment::dsl::*;
insert_into(comment)
.values(comment_form)
.on_conflict(ap_id)
.do_update()
.set(comment_form)
.get_result::<Self>(conn)
}
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]

View File

@ -1,6 +1,7 @@
use crate::{
naive_now,
schema::{community, community_follower, community_moderator, community_user_ban},
ApubObject,
Bannable,
Crud,
Followable,
@ -149,6 +150,25 @@ impl Crud<CommunityForm> for Community {
}
}
impl ApubObject<CommunityForm> for Community {
fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
community
.filter(actor_id.eq(for_actor_id))
.first::<Self>(conn)
}
fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
use crate::schema::community::dsl::*;
insert_into(community)
.values(community_form)
.on_conflict(actor_id)
.do_update()
.set(community_form)
.get_result::<Self>(conn)
}
}
impl Community {
pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
@ -158,13 +178,6 @@ impl Community {
.first::<Self>(conn)
}
pub fn read_from_actor_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
community
.filter(actor_id.eq(for_actor_id))
.first::<Self>(conn)
}
pub fn update_deleted(
conn: &PgConnection,
community_id: i32,
@ -231,16 +244,6 @@ impl Community {
.unwrap_or_default()
.contains(&user_id)
}
pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
use crate::schema::community::dsl::*;
insert_into(community)
.values(community_form)
.on_conflict(actor_id)
.do_update()
.set(community_form)
.get_result::<Self>(conn)
}
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]

View File

@ -123,6 +123,15 @@ pub trait Reportable<T> {
Self: Sized;
}
pub trait ApubObject<T> {
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error>
where
Self: Sized;
fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
where
Self: Sized;
}
pub trait MaybeOptional<T> {
fn get_optional(self) -> Option<T>;
}

View File

@ -1,6 +1,7 @@
use crate::{
naive_now,
schema::{post, post_like, post_read, post_saved},
ApubObject,
Crud,
Likeable,
Readable,
@ -62,6 +63,47 @@ impl PostForm {
}
}
impl Crud<PostForm> for Post {
fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
post.find(post_id).first::<Self>(conn)
}
fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
use crate::schema::post::dsl::*;
diesel::delete(post.find(post_id)).execute(conn)
}
fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
insert_into(post).values(new_post).get_result::<Self>(conn)
}
fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
diesel::update(post.find(post_id))
.set(new_post)
.get_result::<Self>(conn)
}
}
impl ApubObject<PostForm> for Post {
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
post.filter(ap_id.eq(object_id)).first::<Self>(conn)
}
fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> {
use crate::schema::post::dsl::*;
insert_into(post)
.values(post_form)
.on_conflict(ap_id)
.do_update()
.set(post_form)
.get_result::<Self>(conn)
}
}
impl Post {
pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
@ -81,11 +123,6 @@ impl Post {
.load::<Self>(conn)
}
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
post.filter(ap_id.eq(object_id)).first::<Self>(conn)
}
pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
@ -177,40 +214,6 @@ impl Post {
pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool {
user_id == post_creator_id
}
pub fn upsert(conn: &PgConnection, post_form: &PostForm) -> Result<Post, Error> {
use crate::schema::post::dsl::*;
insert_into(post)
.values(post_form)
.on_conflict(ap_id)
.do_update()
.set(post_form)
.get_result::<Self>(conn)
}
}
impl Crud<PostForm> for Post {
fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
post.find(post_id).first::<Self>(conn)
}
fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
use crate::schema::post::dsl::*;
diesel::delete(post.find(post_id)).execute(conn)
}
fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
insert_into(post).values(new_post).get_result::<Self>(conn)
}
fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
diesel::update(post.find(post_id))
.set(new_post)
.get_result::<Self>(conn)
}
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]

View File

@ -1,4 +1,4 @@
use crate::{naive_now, schema::private_message, Crud};
use crate::{naive_now, schema::private_message, ApubObject, Crud};
use diesel::{dsl::*, result::Error, *};
#[derive(Queryable, Identifiable, PartialEq, Debug)]
@ -55,6 +55,28 @@ impl Crud<PrivateMessageForm> for PrivateMessage {
}
}
impl ApubObject<PrivateMessageForm> for PrivateMessage {
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error>
where
Self: Sized,
{
use crate::schema::private_message::dsl::*;
private_message
.filter(ap_id.eq(object_id))
.first::<Self>(conn)
}
fn upsert(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result<Self, Error> {
use crate::schema::private_message::dsl::*;
insert_into(private_message)
.values(private_message_form)
.on_conflict(ap_id)
.do_update()
.set(private_message_form)
.get_result::<Self>(conn)
}
}
impl PrivateMessage {
pub fn update_ap_id(
conn: &PgConnection,
@ -68,13 +90,6 @@ impl PrivateMessage {
.get_result::<Self>(conn)
}
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::private_message::dsl::*;
private_message
.filter(ap_id.eq(object_id))
.first::<Self>(conn)
}
pub fn update_content(
conn: &PgConnection,
private_message_id: i32,
@ -118,20 +133,6 @@ impl PrivateMessage {
.set(read.eq(true))
.get_results::<Self>(conn)
}
// TODO use this
pub fn upsert(
conn: &PgConnection,
private_message_form: &PrivateMessageForm,
) -> Result<Self, Error> {
use crate::schema::private_message::dsl::*;
insert_into(private_message)
.values(private_message_form)
.on_conflict(ap_id)
.do_update()
.set(private_message_form)
.get_result::<Self>(conn)
}
}
#[cfg(test)]

View File

@ -2,6 +2,7 @@ use crate::{
is_email_regex,
naive_now,
schema::{user_, user_::dsl::*},
ApubObject,
Crud,
};
use bcrypt::{hash, DEFAULT_COST};
@ -151,6 +152,25 @@ impl Crud<UserForm> for User_ {
}
}
impl ApubObject<UserForm> for User_ {
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::user_::dsl::*;
user_
.filter(deleted.eq(false))
.filter(actor_id.eq(object_id))
.first::<Self>(conn)
}
fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
insert_into(user_)
.values(user_form)
.on_conflict(actor_id)
.do_update()
.set(user_form)
.get_result::<Self>(conn)
}
}
impl User_ {
pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
let mut edited_user = form.clone();
@ -197,14 +217,6 @@ impl User_ {
.get_result::<Self>(conn)
}
pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::user_::dsl::*;
user_
.filter(deleted.eq(false))
.filter(actor_id.eq(object_id))
.first::<Self>(conn)
}
pub fn find_by_email_or_username(
conn: &PgConnection,
username_or_email: &str,
@ -241,12 +253,9 @@ impl User_ {
)
}
pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
insert_into(user_)
.values(user_form)
.on_conflict(actor_id)
.do_update()
.set(user_form)
pub fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
diesel::update(user_.find(user_id))
.set((last_refreshed_at.eq(naive_now()),))
.get_result::<Self>(conn)
}

View File

@ -11,7 +11,7 @@ path = "src/lib.rs"
[dependencies]
lemmy_db = { path = "../lemmy_db" }
lemmy_utils = { path = "../lemmy_utils" }
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
log = "0.4.11"
diesel = "1.4.5"
actix-web = "3.3.2"

View File

@ -16,7 +16,7 @@ log = "0.4.11"
itertools = "0.9.0"
rand = "0.7.3"
percent-encoding = "2.1.0"
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
thiserror = "1.0.22"
comrak = { version = "0.9.0", default-features = false }
@ -25,5 +25,5 @@ openssl = "0.10.30"
url = { version = "2.2.0", features = ["serde"] }
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-rt = { version = "1.1.1", default-features = false }
anyhow = "1.0.34"
anyhow = "1.0.35"
reqwest = { version = "0.10.9", features = ["json"] }

View File

@ -16,10 +16,10 @@ lemmy_rate_limit = { path = "../lemmy_rate_limit" }
reqwest = { version = "0.10.9", features = ["json"] }
log = "0.4.11"
rand = "0.7.3"
serde = { version = "1.0.117", features = ["derive"] }
serde = { version = "1.0.118", features = ["derive"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] }
actix = "0.10.0"
anyhow = "1.0.34"
anyhow = "1.0.35"
diesel = "1.4.5"
background-jobs = "0.8.0"
tokio = "0.3.5"