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

View File

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

View File

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

View File

@ -37,19 +37,3 @@
- [Zurb mentions](https://github.com/zurb/tribute) - [Zurb mentions](https://github.com/zurb/tribute)
- [TippyJS](https://github.com/atomiks/tippyjs) - [TippyJS](https://github.com/atomiks/tippyjs)
- [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html) - [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. New | Newest items.
Top | The highest scoring items in the given time frame. 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 ## Markdown Guide

View File

@ -4,7 +4,7 @@ Information for Lemmy instance admins, and those who want to run a server.
## Install ## 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 ### Manual install

View File

@ -1,6 +1,6 @@
# Ansible Installation # 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. 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. 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` `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 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 ## 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 ```bash
cd docker/federation 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. 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 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 [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/docker-compose.yml` with `image: dessalines/lemmy:federation`. Also add the following in
`/lemmy/lemmy.hjson`: `/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 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 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 #### Rust
After installing [local development dependencies](contributing_local_development.md), run the After installing [local development dependencies](local_development.md), run the
following commands: following commands:
```bash ```bash
@ -12,7 +12,7 @@ psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
### Federation ### 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 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. 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/c/programming` (Community)
- `https://lemmy.ml/u/nutomic` (User) - `https://lemmy.ml/u/nutomic` (User)
- `https://lemmy.ml/post/123` (Post) - `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). 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. 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": [ "cc": [
"https://ds9.lemmy.ml/c/main/" "https://ds9.lemmy.ml/c/main/"
], ],
"object": ... "object": "https://enterprise.lemmy.ml/p/123"
} }
``` ```
@ -465,7 +467,7 @@ Sent to: Community
"cc": [ "cc": [
"https://ds9.lemmy.ml/c/main/" "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). 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" bcrypt = "0.9.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.60", features = ["preserve_order"] } 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 = "0.10.0"
actix-web = { version = "3.3.2", default-features = false } actix-web = { version = "3.3.2", default-features = false }
actix-rt = { version = "1.1.1", 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" sha2 = "0.9.2"
async-trait = "0.1.42" async-trait = "0.1.42"
captcha = "0.0.8" captcha = "0.0.8"
anyhow = "1.0.34" anyhow = "1.0.35"
thiserror = "1.0.22" thiserror = "1.0.22"
background-jobs = "0.8.0" background-jobs = "0.8.0"
reqwest = { version = "0.10.9", features = ["json"] } reqwest = { version = "0.10.9", features = ["json"] }

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ use activitystreams::{
base::{AnyBase, ExtendsExt}, base::{AnyBase, ExtendsExt},
}; };
use anyhow::Context; 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_structs::{blocking, community::CommunityResponse};
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation}; use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
@ -53,7 +53,7 @@ pub(crate) async fn receive_remove_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let community = blocking(context.pool(), move |conn| { 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??; .await??;
@ -135,7 +135,7 @@ pub(crate) async fn receive_undo_remove_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
let community = blocking(context.pool(), move |conn| { 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??; .await??;

View File

@ -1,19 +1,12 @@
use crate::{ use crate::{activities::receive::get_actor_as_user, objects::FromApub, ActorType, PageExt};
activities::receive::get_actor_as_user,
fetcher::get_or_fetch_and_insert_post,
ActorType,
FromApub,
PageExt,
};
use activitystreams::{ use activitystreams::{
activity::{Create, Dislike, Like, Remove, Update}, activity::{Create, Dislike, Like, Remove, Update},
prelude::*, prelude::*,
}; };
use anyhow::Context; use anyhow::Context;
use lemmy_db::{ use lemmy_db::{
post::{Post, PostForm, PostLike, PostLikeForm}, post::{Post, PostLike, PostLikeForm},
post_view::PostView, post_view::PostView,
Crud,
Likeable, Likeable,
}; };
use lemmy_structs::{blocking, post::PostResponse}; 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!())?)? let page = PageExt::from_any_base(create.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?; let post = Post::from_apub(&page, context, 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??;
// Refetch the view // Refetch the view
let inserted_post_id = inserted_post.id; let post_id = post.id;
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, inserted_post_id, None) PostView::read(conn, post_id, None)
}) })
.await??; .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!())?)? let page = PageExt::from_any_base(update.object().to_owned().one().context(location_info!())?)?
.context(location_info!())?; .context(location_info!())?;
let post = PostForm::from_apub(&page, context, Some(user.actor_id()?), request_counter).await?; let post = Post::from_apub(&page, context, 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_id = post.id;
// Refetch the view // Refetch the view
let post_view = blocking(context.pool(), move |conn| { let post_view = blocking(context.pool(), move |conn| {
PostView::read(conn, original_post_id, None) PostView::read(conn, post_id, None)
}) })
.await??; .await??;
@ -92,19 +73,13 @@ pub(crate) async fn receive_update_post(
pub(crate) async fn receive_like_post( pub(crate) async fn receive_like_post(
like: Like, like: Like,
post: Post,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let user = get_actor_as_user(&like, context, request_counter).await?; 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 { let like_form = PostLikeForm {
post_id, post_id,
user_id: user.id, user_id: user.id,
@ -136,25 +111,13 @@ pub(crate) async fn receive_like_post(
pub(crate) async fn receive_dislike_post( pub(crate) async fn receive_dislike_post(
dislike: Dislike, dislike: Dislike,
post: Post,
context: &LemmyContext, context: &LemmyContext,
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let user = get_actor_as_user(&dislike, context, request_counter).await?; 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 { let like_form = PostLikeForm {
post_id, post_id,
user_id: user.id, user_id: user.id,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -65,7 +65,10 @@ pub async fn sign_and_send(
} }
/// Verifies the HTTP signature on an incoming inbox request. /// 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 public_key = actor.public_key().context(location_info!())?;
let verified = CONFIG2 let verified = CONFIG2
.begin_verify( .begin_verify(

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
http::{create_apub_response, create_apub_tombstone_response}, http::{create_apub_response, create_apub_tombstone_response},
ToApub, objects::ToApub,
}; };
use actix_web::{body::Body, web, HttpResponse}; use actix_web::{body::Body, web, HttpResponse};
use diesel::result::Error::NotFound; 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::{ use activitystreams::{
base::BaseExt, base::BaseExt,
collection::{CollectionExt, OrderedCollection}, collection::{CollectionExt, OrderedCollection},

View File

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

View File

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

View File

@ -31,6 +31,7 @@ use crate::{
receive_unhandled_activity, receive_unhandled_activity,
verify_activity_domains_valid, verify_activity_domains_valid,
}, },
fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
inbox::is_addressed_to_public, inbox::is_addressed_to_public,
}; };
use activitystreams::{ use activitystreams::{
@ -40,7 +41,7 @@ use activitystreams::{
}; };
use anyhow::Context; use anyhow::Context;
use diesel::result::Error::NotFound; 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_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; 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)?; verify_activity_domains_valid(&like, &expected_domain, false)?;
is_addressed_to_public(&like)?; is_addressed_to_public(&like)?;
match like.object().as_single_kind_str() { let object_id = get_like_object_id(&like)?;
Some("Page") => receive_like_post(like, context, request_counter).await, match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
Some("Note") => receive_like_comment(like, context, request_counter).await, PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
_ => receive_unhandled_activity(like), 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)?; verify_activity_domains_valid(&dislike, &expected_domain, false)?;
is_addressed_to_public(&dislike)?; is_addressed_to_public(&dislike)?;
match dislike.object().as_single_kind_str() { let object_id = get_like_object_id(&dislike)?;
Some("Page") => receive_dislike_post(dislike, context, request_counter).await, match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
Some("Note") => receive_dislike_comment(dislike, context, request_counter).await, PostOrComment::Post(post) => {
_ => receive_unhandled_activity(dislike), 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)?; verify_activity_domains_valid(&like, &expected_domain, false)?;
is_addressed_to_public(&like)?; is_addressed_to_public(&like)?;
let type_ = like let object_id = get_like_object_id(&like)?;
.object() match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
.as_single_kind_str() PostOrComment::Post(post) => {
.context(location_info!())?; receive_undo_like_post(&like, post, context, request_counter).await
match type_ { }
"Note" => receive_undo_like_comment(&like, context, request_counter).await, PostOrComment::Comment(comment) => {
"Page" => receive_undo_like_post(&like, context, request_counter).await, receive_undo_like_comment(&like, comment, context, request_counter).await
_ => receive_unhandled_activity(like), }
} }
} }
@ -298,14 +305,14 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
verify_activity_domains_valid(&dislike, &expected_domain, false)?; verify_activity_domains_valid(&dislike, &expected_domain, false)?;
is_addressed_to_public(&dislike)?; is_addressed_to_public(&dislike)?;
let type_ = dislike let object_id = get_like_object_id(&dislike)?;
.object() match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
.as_single_kind_str() PostOrComment::Post(post) => {
.context(location_info!())?; receive_undo_dislike_post(&dislike, post, context, request_counter).await
match type_ { }
"Note" => receive_undo_dislike_comment(&dislike, context, request_counter).await, PostOrComment::Comment(comment) => {
"Page" => receive_undo_dislike_post(&dislike, context, request_counter).await, receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
_ => receive_unhandled_activity(dislike), }
} }
} }
@ -341,3 +348,42 @@ async fn find_post_or_comment_by_id(
return Err(NotFound.into()); 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 activitystreams::{activity::ActorAndObject, prelude::*};
use actix_web::{web, HttpRequest, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::Context; use anyhow::Context;
use lemmy_db::{community::Community, DbPool}; use lemmy_db::{community::Community, ApubObject, DbPool};
use lemmy_structs::blocking; use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError}; use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
@ -137,10 +137,7 @@ async fn extract_local_community_from_destinations(
) -> Result<Option<Community>, LemmyError> { ) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc { for url in to_and_cc {
let url = url.to_string(); let url = url.to_string();
let community = blocking(&pool, move |conn| { let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?;
Community::read_from_actor_id(&conn, &url)
})
.await?;
if let Ok(c) = community { if let Ok(c) = community {
if c.local { if c.local {
return Ok(Some(c)); return Ok(Some(c));

View File

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

View File

@ -18,7 +18,7 @@ use activitystreams::{
activity::Follow, activity::Follow,
actor::{ApActor, Group, Person}, actor::{ApActor, Group, Person},
base::AnyBase, base::AnyBase,
object::{ApObject, Note, Page, Tombstone}, object::{ApObject, Note, Page},
}; };
use activitystreams_ext::{Ext1, Ext2}; use activitystreams_ext::{Ext1, Ext2};
use anyhow::{anyhow, Context}; 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 /// Common functions for ActivityPub objects, which are implemented by most (but not all) objects
/// and actors in Lemmy. /// and actors in Lemmy.
#[async_trait::async_trait(?Send)] #[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 /// Store a sent or received activity in the database, for logging purposes. These records are not
/// persistent. /// persistent.
pub async fn insert_activity<T>( pub(crate) async fn insert_activity<T>(
ap_id: &Url, ap_id: &Url,
activity: T, activity: T,
local: bool, local: bool,

View File

@ -7,19 +7,22 @@ use crate::{
}, },
objects::{ objects::{
check_object_domain, check_object_domain,
check_object_for_community_or_site_ban,
create_tombstone, create_tombstone,
get_object_from_apub,
get_source_markdown_value, get_source_markdown_value,
set_content_and_source, set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
}, },
FromApub,
NoteExt, NoteExt,
ToApub,
}; };
use activitystreams::{ use activitystreams::{
object::{kind::NoteType, ApObject, Note, Tombstone}, object::{kind::NoteType, ApObject, Note, Tombstone},
prelude::*, prelude::*,
}; };
use anyhow::Context; use anyhow::{anyhow, Context};
use lemmy_db::{ use lemmy_db::{
comment::{Comment, CommentForm}, comment::{Comment, CommentForm},
community::Community, community::Community,
@ -87,16 +90,45 @@ impl ToApub for Comment {
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl FromApub for CommentForm { impl FromApub for Comment {
type ApubType = NoteExt; 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. /// If the parent community, post and comment(s) are not known locally, these are also fetched.
async fn from_apub( async fn from_apub(
note: &NoteExt, note: &NoteExt,
context: &LemmyContext, 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, request_counter: &mut i32,
) -> Result<CommentForm, LemmyError> { ) -> Result<CommentForm, LemmyError> {
let creator_actor_id = &note let creator_actor_id = &note

View File

@ -4,13 +4,15 @@ use crate::{
objects::{ objects::{
check_object_domain, check_object_domain,
create_tombstone, create_tombstone,
get_object_from_apub,
get_source_markdown_value, get_source_markdown_value,
set_content_and_source, set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
}, },
ActorType, ActorType,
FromApub,
GroupExt, GroupExt,
ToApub,
}; };
use activitystreams::{ use activitystreams::{
actor::{kind::GroupType, ApActor, Endpoints, Group}, 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) create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
} }
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl FromApub for CommunityForm { impl FromApub for Community {
type ApubType = GroupExt; type ApubType = GroupExt;
/// Converts a `Group` to `Community`.
async fn from_apub( async fn from_apub(
group: &GroupExt, group: &GroupExt,
context: &LemmyContext, 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, request_counter: &mut i32,
) -> Result<Self, LemmyError> { ) -> Result<Self, LemmyError> {
let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?; 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::{ use activitystreams::{
base::{AsBase, BaseExt, ExtendsExt}, base::{AsBase, BaseExt, ExtendsExt},
markers::Base, markers::Base,
@ -7,7 +11,10 @@ use activitystreams::{
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use chrono::NaiveDateTime; 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; use url::Url;
pub(crate) mod comment; pub(crate) mod comment;
@ -16,6 +23,44 @@ pub(crate) mod post;
pub(crate) mod private_message; pub(crate) mod private_message;
pub(crate) mod user; 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 /// Updated is actually the deletion time
fn create_tombstone<T>( fn create_tombstone<T>(
deleted: bool, deleted: bool,
@ -43,17 +88,13 @@ where
pub(in crate::objects) fn check_object_domain<T, Kind>( pub(in crate::objects) fn check_object_domain<T, Kind>(
apub: &T, apub: &T,
expected_domain: Option<Url>, expected_domain: Url,
) -> Result<String, LemmyError> ) -> Result<String, LemmyError>
where where
T: Base + AsBase<Kind>, T: Base + AsBase<Kind>,
{ {
let object_id = if let Some(url) = expected_domain { let domain = expected_domain.domain().context(location_info!())?;
let domain = url.domain().context(location_info!())?; let object_id = apub.id(domain)?.context(location_info!())?;
apub.id(domain)?.context(location_info!())?
} else {
apub.id_unchecked().context(location_info!())?
};
check_is_apub_id_valid(&object_id)?; check_is_apub_id_valid(&object_id)?;
Ok(object_id.to_string()) Ok(object_id.to_string())
} }
@ -127,3 +168,60 @@ pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), L
Ok(()) 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}, fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user},
objects::{ objects::{
check_object_domain, check_object_domain,
check_object_for_community_or_site_ban,
create_tombstone, create_tombstone,
get_object_from_apub,
get_source_markdown_value, get_source_markdown_value,
set_content_and_source, set_content_and_source,
FromApub,
FromApubToForm,
ToApub,
}, },
FromApub,
PageExt, PageExt,
ToApub,
}; };
use activitystreams::{ use activitystreams::{
object::{kind::PageType, ApObject, Image, Page, Tombstone}, object::{kind::PageType, ApObject, Image, Page, Tombstone},
@ -17,7 +20,6 @@ use activitystreams::{
}; };
use activitystreams_ext::Ext1; use activitystreams_ext::Ext1;
use anyhow::Context; use anyhow::Context;
use backtrace::Backtrace;
use lemmy_db::{ use lemmy_db::{
community::Community, community::Community,
post::{Post, PostForm}, post::{Post, PostForm},
@ -33,7 +35,6 @@ use lemmy_utils::{
LemmyError, LemmyError,
}; };
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use log::error;
use url::Url; use url::Url;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
@ -98,7 +99,7 @@ impl ToApub for Post {
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl FromApub for PostForm { impl FromApub for Post {
type ApubType = PageExt; type ApubType = PageExt;
/// Converts a `PageExt` to `PostForm`. /// Converts a `PageExt` to `PostForm`.
@ -107,7 +108,20 @@ impl FromApub for PostForm {
async fn from_apub( async fn from_apub(
page: &PageExt, page: &PageExt,
context: &LemmyContext, 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, request_counter: &mut i32,
) -> Result<PostForm, LemmyError> { ) -> Result<PostForm, LemmyError> {
let ext = &page.ext_one; let ext = &page.ext_one;
@ -132,15 +146,6 @@ impl FromApub for PostForm {
let community = let community =
get_or_fetch_and_upsert_community(community_actor_id, context, request_counter).await?; 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() { let thumbnail_url = match &page.inner.image() {
Some(any_image) => Image::from_any_base( Some(any_image) => Image::from_any_base(
any_image any_image

View File

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

View File

@ -1,10 +1,15 @@
use crate::{ use crate::{
extensions::context::lemmy_context, 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, ActorType,
FromApub,
PersonExt, PersonExt,
ToApub,
}; };
use activitystreams::{ use activitystreams::{
actor::{ApActor, Endpoints, Person}, actor::{ApActor, Endpoints, Person},
@ -16,10 +21,13 @@ use anyhow::Context;
use lemmy_db::{ use lemmy_db::{
naive_now, naive_now,
user::{UserForm, User_}, user::{UserForm, User_},
ApubObject,
DbPool, DbPool,
}; };
use lemmy_structs::blocking;
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
settings::Settings,
utils::{check_slurs, check_slurs_opt, convert_datetime}, utils::{check_slurs, check_slurs_opt, convert_datetime},
LemmyError, LemmyError,
}; };
@ -81,13 +89,38 @@ impl ToApub for User_ {
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl FromApub for UserForm { impl FromApub for User_ {
type ApubType = PersonExt; 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( async fn from_apub(
person: &PersonExt, person: &PersonExt,
_context: &LemmyContext, _context: &LemmyContext,
expected_domain: Option<Url>, expected_domain: Url,
_request_counter: &mut i32, _request_counter: &mut i32,
) -> Result<Self, LemmyError> { ) -> Result<Self, LemmyError> {
let avatar = match person.icon() { let avatar = match person.icon() {

View File

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

View File

@ -2,6 +2,7 @@ use super::post::Post;
use crate::{ use crate::{
naive_now, naive_now,
schema::{comment, comment_like, comment_saved}, schema::{comment, comment_like, comment_saved},
ApubObject,
Crud, Crud,
Likeable, Likeable,
Saveable, 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 { impl Comment {
pub fn update_ap_id( pub fn update_ap_id(
conn: &PgConnection, conn: &PgConnection,
@ -99,11 +117,6 @@ impl Comment {
.get_result::<Self>(conn) .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( pub fn permadelete_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: i32, for_creator_id: i32,
@ -168,16 +181,6 @@ impl Comment {
.set((content.eq(new_content), updated.eq(naive_now()))) .set((content.eq(new_content), updated.eq(naive_now())))
.get_result::<Self>(conn) .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)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
naive_now, naive_now,
schema::{community, community_follower, community_moderator, community_user_ban}, schema::{community, community_follower, community_moderator, community_user_ban},
ApubObject,
Bannable, Bannable,
Crud, Crud,
Followable, 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 { impl Community {
pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> { pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*; use crate::schema::community::dsl::*;
@ -158,13 +178,6 @@ impl Community {
.first::<Self>(conn) .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( pub fn update_deleted(
conn: &PgConnection, conn: &PgConnection,
community_id: i32, community_id: i32,
@ -231,16 +244,6 @@ impl Community {
.unwrap_or_default() .unwrap_or_default()
.contains(&user_id) .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)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]

View File

@ -123,6 +123,15 @@ pub trait Reportable<T> {
Self: Sized; 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> { pub trait MaybeOptional<T> {
fn get_optional(self) -> Option<T>; fn get_optional(self) -> Option<T>;
} }

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
naive_now, naive_now,
schema::{post, post_like, post_read, post_saved}, schema::{post, post_like, post_read, post_saved},
ApubObject,
Crud, Crud,
Likeable, Likeable,
Readable, 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 { impl Post {
pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> { pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
use crate::schema::post::dsl::*; use crate::schema::post::dsl::*;
@ -81,11 +123,6 @@ impl Post {
.load::<Self>(conn) .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> { pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> {
use crate::schema::post::dsl::*; use crate::schema::post::dsl::*;
@ -177,40 +214,6 @@ impl Post {
pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool { pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool {
user_id == post_creator_id 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)] #[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, *}; use diesel::{dsl::*, result::Error, *};
#[derive(Queryable, Identifiable, PartialEq, Debug)] #[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 { impl PrivateMessage {
pub fn update_ap_id( pub fn update_ap_id(
conn: &PgConnection, conn: &PgConnection,
@ -68,13 +90,6 @@ impl PrivateMessage {
.get_result::<Self>(conn) .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( pub fn update_content(
conn: &PgConnection, conn: &PgConnection,
private_message_id: i32, private_message_id: i32,
@ -118,20 +133,6 @@ impl PrivateMessage {
.set(read.eq(true)) .set(read.eq(true))
.get_results::<Self>(conn) .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)] #[cfg(test)]

View File

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

View File

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

View File

@ -16,7 +16,7 @@ log = "0.4.11"
itertools = "0.9.0" itertools = "0.9.0"
rand = "0.7.3" rand = "0.7.3"
percent-encoding = "2.1.0" 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"] } serde_json = { version = "1.0.60", features = ["preserve_order"] }
thiserror = "1.0.22" thiserror = "1.0.22"
comrak = { version = "0.9.0", default-features = false } comrak = { version = "0.9.0", default-features = false }
@ -25,5 +25,5 @@ openssl = "0.10.30"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.0", features = ["serde"] }
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] } actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-rt = { version = "1.1.1", default-features = false } actix-rt = { version = "1.1.1", default-features = false }
anyhow = "1.0.34" anyhow = "1.0.35"
reqwest = { version = "0.10.9", features = ["json"] } 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"] } reqwest = { version = "0.10.9", features = ["json"] }
log = "0.4.11" log = "0.4.11"
rand = "0.7.3" 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"] } serde_json = { version = "1.0.60", features = ["preserve_order"] }
actix = "0.10.0" actix = "0.10.0"
anyhow = "1.0.34" anyhow = "1.0.35"
diesel = "1.4.5" diesel = "1.4.5"
background-jobs = "0.8.0" background-jobs = "0.8.0"
tokio = "0.3.5" tokio = "0.3.5"